]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-154749: Reject a terminal-less screen in curses.set_term() (GH-154750)
authorVyron Vasileiadis <hi@fedonman.com>
Mon, 27 Jul 2026 18:59:46 +0000 (21:59 +0300)
committerGitHub <noreply@github.com>
Mon, 27 Jul 2026 18:59:46 +0000 (18:59 +0000)
set_term() accepted a screen returned by new_prescr(), which has no
terminal, and the next refresh crashed inside curses.  Raise
curses.error instead.

Doc/library/curses.rst
Lib/test/test_curses.py
Modules/_cursesmodule.c

index 9d0bb239af06dfce34db435c8a54e3c91917d325..a833914a5d56d44b6dd5f462e12585c02959d7bf 100644 (file)
@@ -129,6 +129,8 @@ Initialization and termination
    and return the previously current screen.
    Returns ``None`` if the previous screen was the one created by
    :func:`initscr`.
+   Raises :exc:`error` if *screen* has no terminal,
+   as is the case for a screen returned by :func:`new_prescr`.
 
    .. versionadded:: next
 
index 31b7371abd32300efb0d7f8e317cb1fc8e4646c0..441eafd6e0d47765dc2c7af8423d121f77854d4f 100644 (file)
@@ -3072,6 +3072,17 @@ class ScreenTests(NewtermTestBase):
         del screen
         gc_collect()
 
+    @requires_curses_func('new_prescr')
+    def test_set_term_prescr_screen(self):
+        # A new_prescr() screen has no terminal, so it cannot become the
+        # current one.  It used to be accepted, and the next refresh then
+        # crashed inside curses.
+        s = self.make_pty()
+        screen = curses.newterm('xterm', s, s)
+        self.assertRaises(curses.error, curses.set_term, curses.new_prescr())
+        # The current screen is unchanged, so refreshing it still works.
+        screen.stdscr.refresh()
+
     def test_initscr_after_newterm_keeps_screen_alive(self):
         # initscr() called while a newterm() screen is current returns that
         # screen's own standard window, so the window keeps the screen alive.
index 2b580c3475e6d93ba3dd585f2c151eaed32fe1dc..7314708cff7a814e4e9f5e23afad663752fd6242 100644 (file)
@@ -6850,11 +6850,17 @@ _curses_set_term(PyObject *module, PyObject *screen)
     if (so == NULL) {
         return NULL;
     }
+    cursesmodule_state *state = get_cursesmodule_state(module);
+    if (so->stdscr_win == NULL) {
+        /* A screen from new_prescr() has no terminal, so it cannot become the
+           current one: a later refresh would dereference NULL in curses. */
+        PyErr_SetString(state->error, "the screen has no terminal");
+        return NULL;
+    }
     set_term(so->screen);
     if (!update_lines_cols(module)) {
         return NULL;
     }
-    cursesmodule_state *state = get_cursesmodule_state(module);
     PyObject *prev = state->topscreen;          /* steal the owned reference */
     state->topscreen = Py_NewRef(screen);
     return prev != NULL ? prev : Py_NewRef(Py_None);