Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c8eb79d

Browse filesBrowse files
gh-154786: Fix crashes on a screen without a terminal (GH-154787)
screen.use() makes its screen current for the callback, so a new_prescr() screen, which has no terminal, can be current without set_term(). Operations that need a terminal then crashed inside curses. Raise curses.error instead, checking that stdscr exists.
1 parent 22a123f commit c8eb79d
Copy full SHA for c8eb79d

2 files changed

+126-4Lines changed: 126 additions & 4 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎Lib/test/test_curses.py‎

Copy file name to clipboardExpand all lines: Lib/test/test_curses.py
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3083,6 +3083,30 @@ def test_set_term_prescr_screen(self):
30833083
# The current screen is unchanged, so refreshing it still works.
30843084
screen.stdscr.refresh()
30853085

3086+
@unittest.skipUnless(hasattr(curses, 'new_prescr'),
3087+
'requires curses.new_prescr()')
3088+
@unittest.skipUnless(hasattr(curses.screen, 'use'),
3089+
'requires curses.screen.use()')
3090+
def test_use_prescr_screen(self):
3091+
# use() makes its screen current for the callback, so a new_prescr()
3092+
# screen is current there without having a terminal. Operations that
3093+
# need one used to crash inside curses.
3094+
s = self.make_pty()
3095+
screen = curses.newterm('xterm', s, s)
3096+
prescr = curses.new_prescr()
3097+
for func in [
3098+
lambda scr: curses.doupdate(),
3099+
lambda scr: curses.newwin(3, 3),
3100+
lambda scr: screen.stdscr.refresh(),
3101+
lambda scr: screen.stdscr.getch(),
3102+
]:
3103+
with self.assertRaises(curses.error):
3104+
prescr.use(func)
3105+
# Affecting the state before initscr() is what such a screen is for.
3106+
prescr.use(lambda scr: curses.use_env(False))
3107+
# The current screen is unchanged.
3108+
screen.stdscr.refresh()
3109+
30863110
def test_initscr_after_newterm_keeps_screen_alive(self):
30873111
# initscr() called while a newterm() screen is current returns that
30883112
# screen's own standard window, so the window keeps the screen alive.
Collapse file

‎Modules/_cursesmodule.c‎

Copy file name to clipboardExpand all lines: Modules/_cursesmodule.c
+102-4Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,35 @@ _PyCursesStatefulCheckFunction(PyObject *module,
356356
return 0;
357357
}
358358

359+
/*
360+
* Function to check that the current screen has a terminal, by testing
361+
* stdscr, which a screen made by new_prescr() does not have. If an error
362+
* occurs, a PyCursesError is set and this returns 0. Otherwise this
363+
* returns 1.
364+
*/
365+
static int
366+
_PyCursesStatefulCheckTerminal(PyObject *module)
367+
{
368+
if (stdscr != NULL) {
369+
return 1;
370+
}
371+
cursesmodule_state *state = get_cursesmodule_state(module);
372+
PyErr_SetString(state->error, "the current screen has no terminal");
373+
return 0;
374+
}
375+
376+
/* Same as _PyCursesStatefulCheckTerminal() for a Window object. */
377+
static int
378+
curses_window_check_terminal(PyCursesWindowObject *win)
379+
{
380+
if (stdscr != NULL) {
381+
return 1;
382+
}
383+
cursesmodule_state *state = get_cursesmodule_state_by_win(win);
384+
PyErr_SetString(state->error, "the current screen has no terminal");
385+
return 0;
386+
}
387+
359388
#define PyCursesStatefulSetupTermCalled(MODULE) \
360389
do { \
361390
if (!_PyCursesStatefulCheckFunction(MODULE, \
@@ -370,7 +399,8 @@ _PyCursesStatefulCheckFunction(PyObject *module,
370399
do { \
371400
if (!_PyCursesStatefulCheckFunction(MODULE, \
372401
curses_initscr_called, \
373-
"initscr")) \
402+
"initscr") \
403+
|| !_PyCursesStatefulCheckTerminal(MODULE)) \
374404
{ \
375405
return 0; \
376406
} \
@@ -1793,6 +1823,42 @@ curses_window_put_cells(PyCursesWindowObject *self, PyObject *obj,
17931823
Py_RETURN_NONE; \
17941824
}
17951825

1826+
/* Same as Window_OneArgNoReturnVoidFunction() for a function that needs
1827+
a terminal. */
1828+
#define Window_OneArgNoReturnVoidTerminalFunction(X, TYPE, PARSESTR) \
1829+
static PyObject * PyCursesWindow_ ## X \
1830+
(PyObject *op, PyObject *args) \
1831+
{ \
1832+
TYPE arg1; \
1833+
if (!PyArg_ParseTuple(args, PARSESTR, &arg1)) { \
1834+
return NULL; \
1835+
} \
1836+
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
1837+
if (!curses_window_check_terminal(self)) { \
1838+
return NULL; \
1839+
} \
1840+
X(self->win, arg1); \
1841+
Py_RETURN_NONE; \
1842+
}
1843+
1844+
/* Same as Window_OneArgNoReturnFunction() for a function that needs
1845+
a terminal. */
1846+
#define Window_OneArgNoReturnTerminalFunction(X, TYPE, PARSESTR) \
1847+
static PyObject * PyCursesWindow_ ## X \
1848+
(PyObject *op, PyObject *args) \
1849+
{ \
1850+
TYPE arg1; \
1851+
if (!PyArg_ParseTuple(args, PARSESTR, &arg1)) { \
1852+
return NULL; \
1853+
} \
1854+
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op); \
1855+
if (!curses_window_check_terminal(self)) { \
1856+
return NULL; \
1857+
} \
1858+
int code = X(self->win, arg1); \
1859+
return curses_window_check_err(self, code, # X, NULL); \
1860+
}
1861+
17961862
#define Window_OneArgNoReturnFunction(X, TYPE, PARSESTR) \
17971863
static PyObject * PyCursesWindow_ ## X \
17981864
(PyObject *op, PyObject *args) \
@@ -1893,7 +1959,7 @@ Window_NoArgNoReturnVoidFunction(wclrtoeol)
18931959
Window_NoArgNoReturnVoidFunction(wclrtobot)
18941960
Window_NoArgNoReturnVoidFunction(wclear)
18951961

1896-
Window_OneArgNoReturnVoidFunction(idcok, int, "i;True(1) or False(0)")
1962+
Window_OneArgNoReturnVoidTerminalFunction(idcok, int, "i;True(1) or False(0)")
18971963
#ifdef HAVE_CURSES_IMMEDOK
18981964
Window_OneArgNoReturnVoidFunction(immedok, int, "i;True(1) or False(0)")
18991965
#endif
@@ -1905,8 +1971,8 @@ Window_NoArg2TupleReturnFunction(getmaxyx, int, "ii")
19051971
Window_NoArg2TupleReturnFunction(getparyx, int, "ii")
19061972

19071973
Window_OneArgNoReturnFunction(clearok, int, "i;True(1) or False(0)")
1908-
Window_OneArgNoReturnFunction(idlok, int, "i;True(1) or False(0)")
1909-
Window_OneArgNoReturnFunction(keypad, int, "i;True(1) or False(0)")
1974+
Window_OneArgNoReturnTerminalFunction(idlok, int, "i;True(1) or False(0)")
1975+
Window_OneArgNoReturnTerminalFunction(keypad, int, "i;True(1) or False(0)")
19101976
Window_OneArgNoReturnFunction(leaveok, int, "i;True(1) or False(0)")
19111977
Window_OneArgNoReturnFunction(nodelay, int, "i;True(1) or False(0)")
19121978
Window_OneArgNoReturnFunction(notimeout, int, "i;True(1) or False(0)")
@@ -2960,6 +3026,10 @@ _curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
29603026
int group_right_1, attr_t attr)
29613027
/*[clinic end generated code: output=ab03afa580aa6a2a input=cd74c42aadcc7e30]*/
29623028
{
3029+
if (!curses_window_check_terminal(self)) {
3030+
return NULL;
3031+
}
3032+
29633033
chtype ch_;
29643034
#ifdef HAVE_NCURSESW
29653035
cchar_t wch;
@@ -3206,6 +3276,10 @@ _curses_window_getch_impl(PyCursesWindowObject *self, int group_right_1,
32063276
{
32073277
int rtn;
32083278

3279+
if (!curses_window_check_terminal(self)) {
3280+
return NULL;
3281+
}
3282+
32093283
Py_BEGIN_ALLOW_THREADS
32103284
if (!group_right_1) {
32113285
rtn = wgetch(self->win);
@@ -3254,6 +3328,10 @@ _curses_window_getkey_impl(PyCursesWindowObject *self, int group_right_1,
32543328
{
32553329
int rtn;
32563330

3331+
if (!curses_window_check_terminal(self)) {
3332+
return NULL;
3333+
}
3334+
32573335
Py_BEGIN_ALLOW_THREADS
32583336
if (!group_right_1) {
32593337
rtn = wgetch(self->win);
@@ -3305,6 +3383,10 @@ _curses_window_get_wch_impl(PyCursesWindowObject *self, int group_right_1,
33053383
int y, int x)
33063384
/*[clinic end generated code: output=9f4f86e91fe50ef3 input=dd7e5367fb49dc48]*/
33073385
{
3386+
if (!curses_window_check_terminal(self)) {
3387+
return NULL;
3388+
}
3389+
33083390
#ifdef HAVE_NCURSESW
33093391
int ct;
33103392
wint_t rtn;
@@ -3454,6 +3536,10 @@ static PyObject *
34543536
PyCursesWindow_getstr(PyObject *op, PyObject *args)
34553537
{
34563538
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
3539+
3540+
if (!curses_window_check_terminal(self)) {
3541+
return NULL;
3542+
}
34573543
return curses_window_getstr_bytes(self, args, "_curses.window.getstr");
34583544
}
34593545

@@ -3755,6 +3841,10 @@ static PyObject *
37553841
PyCursesWindow_get_wstr(PyObject *op, PyObject *args)
37563842
{
37573843
PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
3844+
3845+
if (!curses_window_check_terminal(self)) {
3846+
return NULL;
3847+
}
37583848
#ifdef HAVE_NCURSESW
37593849
int rtn, use_xy = 0, y = 0, x = 0;
37603850
unsigned int max_buf_size = 2048;
@@ -4271,6 +4361,10 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject *self)
42714361
{
42724362
int rtn;
42734363

4364+
if (!curses_window_check_terminal(self)) {
4365+
return NULL;
4366+
}
4367+
42744368
#ifdef py_is_pad
42754369
if (py_is_pad(self->win)) {
42764370
if (!group_right_1) {
@@ -4501,6 +4595,10 @@ _curses_window_refresh_impl(PyCursesWindowObject *self, int group_right_1,
45014595
{
45024596
int rtn;
45034597

4598+
if (!curses_window_check_terminal(self)) {
4599+
return NULL;
4600+
}
4601+
45044602
#ifdef py_is_pad
45054603
if (py_is_pad(self->win)) {
45064604
if (!group_right_1) {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.