Skip to content

Commit 0af61fe

Browse files
bpo-44172: Keep reference to original window in curses subwindow objects (GH-26226)
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 Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent ac7d5ba commit 0af61fe

File tree

4 files changed

+28
-10
lines changed

4 files changed

+28
-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 PyCurses_CAPSULE_NAME "_curses._C_API"

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
@@ -181,6 +182,14 @@ def test_create_windows(self):
181182
self.assertEqual(win3.getparyx(), (2, 1))
182183
self.assertEqual(win3.getmaxyx(), (6, 11))
183184

185+
def test_subwindows_references(self):
186+
win = curses.newwin(5, 10)
187+
win2 = win.subwin(3, 7)
188+
del win
189+
gc_collect()
190+
del win2
191+
gc_collect()
192+
184193
def test_move_cursor(self):
185194
stdscr = self.stdscr
186195
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: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,8 @@ Window_TwoArgNoReturnFunction(wresize, int, "ii;lines,columns")
787787

788788
static PyObject *
789789
PyCursesWindow_New(cursesmodule_state *state,
790-
WINDOW *win, const char *encoding)
790+
WINDOW *win, const char *encoding,
791+
PyCursesWindowObject *orig)
791792
{
792793
if (encoding == NULL) {
793794
#if defined(MS_WINDOWS)
@@ -821,6 +822,8 @@ PyCursesWindow_New(cursesmodule_state *state,
821822
PyErr_NoMemory();
822823
return NULL;
823824
}
825+
wo->orig = orig;
826+
Py_XINCREF(orig);
824827
PyObject_GC_Track((PyObject *)wo);
825828
return (PyObject *)wo;
826829
}
@@ -838,6 +841,7 @@ PyCursesWindow_dealloc(PyObject *self)
838841
if (wo->encoding != NULL) {
839842
PyMem_Free(wo->encoding);
840843
}
844+
Py_XDECREF(wo->orig);
841845
window_type->tp_free(self);
842846
Py_DECREF(window_type);
843847
}
@@ -846,6 +850,8 @@ static int
846850
PyCursesWindow_traverse(PyObject *self, visitproc visit, void *arg)
847851
{
848852
Py_VISIT(Py_TYPE(self));
853+
PyCursesWindowObject *wo = (PyCursesWindowObject *)self;
854+
Py_VISIT(wo->orig);
849855
return 0;
850856
}
851857

@@ -1453,7 +1459,7 @@ _curses_window_derwin_impl(PyCursesWindowObject *self, int group_left_1,
14531459
}
14541460

14551461
cursesmodule_state *state = get_cursesmodule_state_by_win(self);
1456-
return PyCursesWindow_New(state, win, NULL);
1462+
return PyCursesWindow_New(state, win, NULL, self);
14571463
}
14581464

14591465
/*[clinic input]
@@ -2493,7 +2499,7 @@ _curses_window_subwin_impl(PyCursesWindowObject *self, int group_left_1,
24932499
}
24942500

24952501
cursesmodule_state *state = get_cursesmodule_state_by_win(self);
2496-
return PyCursesWindow_New(state, win, self->encoding);
2502+
return PyCursesWindow_New(state, win, self->encoding, self);
24972503
}
24982504

24992505
/*[clinic input]
@@ -3237,7 +3243,7 @@ _curses_getwin(PyObject *module, PyObject *file)
32373243
goto error;
32383244
}
32393245
cursesmodule_state *state = get_cursesmodule_state(module);
3240-
res = PyCursesWindow_New(state, win, NULL);
3246+
res = PyCursesWindow_New(state, win, NULL, NULL);
32413247

32423248
error:
32433249
fclose(fp);
@@ -3410,7 +3416,7 @@ _curses_initscr_impl(PyObject *module)
34103416
if (curses_initscr_called) {
34113417
wrefresh(stdscr);
34123418
cursesmodule_state *state = get_cursesmodule_state(module);
3413-
return PyCursesWindow_New(state, stdscr, NULL);
3419+
return PyCursesWindow_New(state, stdscr, NULL, NULL);
34143420
}
34153421

34163422
win = initscr();
@@ -3514,7 +3520,7 @@ _curses_initscr_impl(PyObject *module)
35143520
#undef SetDictInt
35153521

35163522
cursesmodule_state *state = get_cursesmodule_state(module);
3517-
PyObject *winobj = PyCursesWindow_New(state, win, NULL);
3523+
PyObject *winobj = PyCursesWindow_New(state, win, NULL, NULL);
35183524
if (winobj == NULL) {
35193525
return NULL;
35203526
}
@@ -3898,7 +3904,7 @@ _curses_newpad_impl(PyObject *module, int nlines, int ncols)
38983904
}
38993905

39003906
cursesmodule_state *state = get_cursesmodule_state(module);
3901-
return PyCursesWindow_New(state, win, NULL);
3907+
return PyCursesWindow_New(state, win, NULL, NULL);
39023908
}
39033909

39043910
/*[clinic input]
@@ -3939,7 +3945,7 @@ _curses_newwin_impl(PyObject *module, int nlines, int ncols,
39393945
}
39403946

39413947
cursesmodule_state *state = get_cursesmodule_state(module);
3942-
return PyCursesWindow_New(state, win, NULL);
3948+
return PyCursesWindow_New(state, win, NULL, NULL);
39433949
}
39443950

39453951
/*[clinic input]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy