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)
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