https://github.com/python/cpython/commit/30dde1eeb3fa1e0e7417f9cdded8fd90766f2559
commit: 30dde1eeb3fa1e0e7417f9cdded8fd90766f2559
branch: main
author: Bénédikt Tran <[email protected]>
committer: picnixz <[email protected]>
date: 2025-05-27T10:15:16Z
summary:
gh-133579: consistently report C curses function failures (#134327)
Some curses module-level functions and window methods now raise
a `curses.error` when a call to a C curses function fails:
- Module-level functions: assume_default_colors, baudrate, cbreak,
echo, longname, initscr, nl, raw, termattrs, termname, and unctrl.
- Window methods: addch, addnstr, addstr, border, box, chgat,
getbkgd, inch, insstr, and insnstr.
In addition, `curses.window.refresh` and `curses.window.noutrefresh`
now raise a `TypeError` instead of a `curses.error` when called with an
incorrect number of arguments for pads.
See also ee36db550076e5a9185444ffbc53eaf8157ef04c for similar
changes.
files:
A Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst
A Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst
A Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst
M Doc/howto/curses.rst
M Modules/_cursesmodule.c
M Modules/clinic/_cursesmodule.c.h
diff --git a/Doc/howto/curses.rst b/Doc/howto/curses.rst
index 6994a5328e8149..816639552d7cd6 100644
--- a/Doc/howto/curses.rst
+++ b/Doc/howto/curses.rst
@@ -161,6 +161,8 @@ your terminal won't be left in a funny state on exception
and you'll be
able to read the exception's message and traceback.
+.. _windows-and-pads:
+
Windows and Pads
================
diff --git
a/Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst
b/Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst
new file mode 100644
index 00000000000000..129d5d9842576b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-27-11-13-51.gh-issue-133579.KY9M6S.rst
@@ -0,0 +1,8 @@
+:ref:`curses.window <curses-window-objects>`: Consistently report failures
+of curses C API calls in Window methods by raising a :exc:`curses.error`.
+This affects :meth:`~curses.window.addch`, :meth:`~curses.window.addnstr`,
+:meth:`~curses.window.addstr`, :meth:`~curses.window.border`,
+:meth:`~curses.window.box`, :meth:`~curses.window.chgat`,
+:meth:`~curses.window.getbkgd`, :meth:`~curses.window.inch`,
+:meth:`~curses.window.insstr` and :meth:`~curses.window.insnstr`.
+Patch by Bénédikt Tran.
diff --git
a/Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst
b/Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst
new file mode 100644
index 00000000000000..e0ef959f125d62
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-27-11-18-13.gh-issue-133579.ohtgdC.rst
@@ -0,0 +1,3 @@
+:meth:`curses.window.refresh` and :meth:`curses.window.noutrefresh` now raise
+a :exc:`TypeError` instead of :exc:`curses.error` when called with an incorrect
+number of arguments for :ref:`pads <windows-and-pads>`. Patch by Bénédikt Tran.
diff --git
a/Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst
b/Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst
new file mode 100644
index 00000000000000..552b7ca1a7187d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-27-11-24-38.gh-issue-133579.WGPUC1.rst
@@ -0,0 +1,7 @@
+:mod:`curses`: Consistently report failures of curses C API calls in
+module-level methods by raising a :exc:`curses.error`. This affects
+:func:`~curses.assume_default_colors`, :func:`~curses.baudrate`,
+:func:`~curses.cbreak`, :func:`~curses.echo`, :func:`~curses.longname`,
+:func:`~curses.initscr`, :func:`~curses.nl`, :func:`~curses.raw`,
+:func:`~curses.termattrs`, :func:`~curses.termname` and :func:`~curses.unctrl`.
+Patch by Bénédikt Tran.
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index 21c2509efe816a..290ae4e55cd7a7 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -932,8 +932,10 @@ PyCursesWindow_dealloc(PyObject *self)
PyObject_GC_UnTrack(self);
PyCursesWindowObject *wo = (PyCursesWindowObject *)self;
if (wo->win != stdscr && wo->win != NULL) {
- // silently ignore errors in delwin(3)
- (void)delwin(wo->win);
+ if (delwin(wo->win) == ERR) {
+ curses_window_set_error(wo, "delwin", "__del__");
+ PyErr_FormatUnraisable("Exception ignored in delwin()");
+ }
}
if (wo->encoding != NULL) {
PyMem_Free(wo->encoding);
@@ -1001,7 +1003,11 @@ _curses_window_addch_impl(PyCursesWindowObject *self,
int group_left_1,
type = PyCurses_ConvertToCchar_t(self, ch, &cch, wstr);
if (type == 2) {
wstr[1] = L'\0';
- setcchar(&wcval, wstr, attr, PAIR_NUMBER(attr), NULL);
+ rtn = setcchar(&wcval, wstr, attr, PAIR_NUMBER(attr), NULL);
+ if (rtn == ERR) {
+ curses_window_set_error(self, "setcchar", "addch");
+ return NULL;
+ }
if (coordinates_group) {
rtn = mvwadd_wch(self->win,y,x, &wcval);
funcname = "mvwadd_wch";
@@ -1031,6 +1037,27 @@ _curses_window_addch_impl(PyCursesWindowObject *self,
int group_left_1,
return curses_window_check_err(self, rtn, funcname, "addch");
}
+#ifdef HAVE_NCURSESW
+#define curses_release_wstr(STRTYPE, WSTR) \
+ do { \
+ if ((STRTYPE) == 2) { \
+ PyMem_Free((WSTR)); \
+ } \
+ } while (0)
+#else
+#define curses_release_wstr(_STRTYPE, _WSTR)
+#endif
+
+static int
+curses_wattrset(PyCursesWindowObject *self, long attr, const char *funcname)
+{
+ if (wattrset(self->win, attr) == ERR) {
+ curses_window_set_error(self, "wattrset", funcname);
+ return -1;
+ }
+ return 0;
+}
+
/*[clinic input]
_curses.window.addstr
@@ -1084,7 +1111,10 @@ _curses_window_addstr_impl(PyCursesWindowObject *self,
int group_left_1,
}
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win,attr);
+ if (curses_wattrset(self, attr, "addstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
@@ -1112,9 +1142,15 @@ _curses_window_addstr_impl(PyCursesWindowObject *self,
int group_left_1,
}
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return curses_window_check_err(self, rtn, funcname, "addstr");
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "addstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "addstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -1173,7 +1209,10 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self,
int group_left_1,
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win,attr);
+ if (curses_wattrset(self, attr, "addnstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
@@ -1201,9 +1240,15 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self,
int group_left_1,
}
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return curses_window_check_err(self, rtn, funcname, "addnstr");
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "addnstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "addnstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -1345,7 +1390,7 @@ _curses_window_border_impl(PyCursesWindowObject *self,
PyObject *ls,
/*[clinic end generated code: output=670ef38d3d7c2aa3 input=e015f735d67a240b]*/
{
chtype ch[8];
- int i;
+ int i, rtn;
/* Clear the array of parameters */
for(i=0; i<8; i++)
@@ -1366,10 +1411,10 @@ _curses_window_border_impl(PyCursesWindowObject *self,
PyObject *ls,
#undef CONVERTTOCHTYPE
- wborder(self->win,
- ch[0], ch[1], ch[2], ch[3],
- ch[4], ch[5], ch[6], ch[7]);
- Py_RETURN_NONE;
+ rtn = wborder(self->win,
+ ch[0], ch[1], ch[2], ch[3],
+ ch[4], ch[5], ch[6], ch[7]);
+ return curses_window_check_err(self, rtn, "wborder", "border");
}
/*[clinic input]
@@ -1403,8 +1448,7 @@ _curses_window_box_impl(PyCursesWindowObject *self, int
group_right_1,
return NULL;
}
}
- box(self->win,ch1,ch2);
- Py_RETURN_NONE;
+ return curses_window_check_err(self, box(self->win, ch1, ch2), "box",
NULL);
}
#if defined(HAVE_NCURSES_H) || defined(MVWDELCH_IS_EXPRESSION)
@@ -1498,15 +1542,18 @@ PyCursesWindow_ChgAt(PyObject *op, PyObject *args)
if (use_xy) {
rtn = mvwchgat(self->win,y,x,num,attr,color,NULL);
- touchline(self->win,y,1);
funcname = "mvwchgat";
} else {
getyx(self->win,y,x);
rtn = wchgat(self->win,num,attr,color,NULL);
- touchline(self->win,y,1);
funcname = "wchgat";
}
- return curses_window_check_err(self, rtn, funcname, "chgat");
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "chgat");
+ return NULL;
+ }
+ rtn = touchline(self->win,y,1);
+ return curses_window_check_err(self, rtn, "touchline", "chgat");
}
#endif
@@ -1643,16 +1690,21 @@ _curses_window_enclose_impl(PyCursesWindowObject *self,
int y, int x)
#endif
/*[clinic input]
-_curses.window.getbkgd -> long
+_curses.window.getbkgd
Return the window's current background character/attribute pair.
[clinic start generated code]*/
-static long
+static PyObject *
_curses_window_getbkgd_impl(PyCursesWindowObject *self)
-/*[clinic end generated code: output=c52b25dc16b215c3 input=a69db882fa35426c]*/
+/*[clinic end generated code: output=3ff953412b0e6028 input=7cf1f59a31f89df4]*/
{
- return (long) getbkgd(self->win);
+ chtype rtn = getbkgd(self->win);
+ if (rtn == (chtype)ERR) {
+ curses_window_set_error(self, "getbkgd", NULL);
+ return NULL;
+ }
+ return PyLong_FromLong(rtn);
}
static PyObject *
@@ -2011,7 +2063,7 @@ _curses_window_insch_impl(PyCursesWindowObject *self, int
group_left_1,
}
/*[clinic input]
-_curses.window.inch -> unsigned_long
+_curses.window.inch
[
y: int
@@ -2026,21 +2078,27 @@ Return the character at the given position in the
window.
The bottom 8 bits are the character proper, and upper bits are the attributes.
[clinic start generated code]*/
-static unsigned long
+static PyObject *
_curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x)
-/*[clinic end generated code: output=6c4719fe978fe86a input=fac23ee11e3b3a66]*/
+/*[clinic end generated code: output=97ca8581baaafd06 input=4b4fb43d85b177c3]*/
{
- unsigned long rtn;
+ chtype rtn;
+ const char *funcname;
if (!group_right_1) {
rtn = winch(self->win);
+ funcname = "winch";
}
else {
rtn = mvwinch(self->win, y, x);
+ funcname = "mvwinch";
}
-
- return rtn;
+ if (rtn == (chtype)ERR) {
+ curses_window_set_error(self, funcname, "inch");
+ return NULL;
+ }
+ return PyLong_FromUnsignedLong(rtn);
}
PyDoc_STRVAR(_curses_window_instr__doc__,
@@ -2150,7 +2208,10 @@ _curses_window_insstr_impl(PyCursesWindowObject *self,
int group_left_1,
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win, (attr_t)attr);
+ if (curses_wattrset(self, attr, "insstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
@@ -2178,9 +2239,15 @@ _curses_window_insstr_impl(PyCursesWindowObject *self,
int group_left_1,
}
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return curses_window_check_err(self, rtn, funcname, "insstr");
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "insstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "insstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -2241,7 +2308,10 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self,
int group_left_1,
if (use_attr) {
attr_old = getattrs(self->win);
- (void)wattrset(self->win, (attr_t)attr);
+ if (curses_wattrset(self, attr, "insnstr") < 0) {
+ curses_release_wstr(strtype, wstr);
+ return NULL;
+ }
}
#ifdef HAVE_NCURSESW
if (strtype == 2) {
@@ -2269,9 +2339,15 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self,
int group_left_1,
}
Py_DECREF(bytesobj);
}
- if (use_attr)
- (void)wattrset(self->win,attr_old);
- return curses_window_check_err(self, rtn, funcname, "insnstr");
+ if (rtn == ERR) {
+ curses_window_set_error(self, funcname, "insnstr");
+ return NULL;
+ }
+ if (use_attr) {
+ rtn = wattrset(self->win, attr_old);
+ return curses_window_check_err(self, rtn, "wattrset", "insnstr");
+ }
+ Py_RETURN_NONE;
}
/*[clinic input]
@@ -2347,8 +2423,7 @@ _curses_window_noutrefresh_impl(PyCursesWindowObject
*self)
#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error,
+ PyErr_SetString(PyExc_TypeError,
"noutrefresh() called for a pad "
"requires 6 arguments");
return NULL;
@@ -2574,8 +2649,7 @@ _curses_window_refresh_impl(PyCursesWindowObject *self,
int group_right_1,
#ifdef py_is_pad
if (py_is_pad(self->win)) {
if (!group_right_1) {
- cursesmodule_state *state = get_cursesmodule_state_by_win(self);
- PyErr_SetString(state->error,
+ PyErr_SetString(PyExc_TypeError,
"refresh() for a pad requires 6 arguments");
return NULL;
}
@@ -2964,12 +3038,15 @@ static PyType_Spec PyCursesWindow_Type_spec = {
*
* These macros should only be used for generating the body of
* the module's methods since they need a module reference.
+ *
+ * The Python function name must be the same as the curses function name (X).
*/
-#define NoArgNoReturnFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return curses_check_err(module, X(), # X, NULL); }
+#define NoArgNoReturnFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ return curses_check_err(module, X(), # X, NULL); \
+}
#define NoArgOrFlagNoReturnFunctionBody(X, FLAG) \
{ \
@@ -2987,26 +3064,40 @@ static PyType_Spec PyCursesWindow_Type_spec = {
return curses_check_err(module, rtn, funcname, # X); \
}
-#define NoArgReturnIntFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyLong_FromLong((long) X()); }
+#define NoArgReturnIntFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ int rtn = X(); \
+ if (rtn == ERR) { \
+ curses_set_error(module, # X, NULL); \
+ return NULL; \
+ } \
+ return PyLong_FromLong(rtn); \
+}
-#define NoArgReturnStringFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyBytes_FromString(X()); }
+#define NoArgReturnStringFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ const char *res = X(); \
+ if (res == NULL) { \
+ curses_set_null_error(module, # X, NULL); \
+ return NULL; \
+ } \
+ return PyBytes_FromString(res); \
+}
-#define NoArgTrueFalseFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- return PyBool_FromLong(X()); }
+#define NoArgTrueFalseFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ return PyBool_FromLong(X()); \
+}
-#define NoArgNoReturnVoidFunctionBody(X) \
-{ \
- PyCursesStatefulInitialised(module); \
- X(); \
- Py_RETURN_NONE; }
+#define NoArgNoReturnVoidFunctionBody(X) \
+{ \
+ PyCursesStatefulInitialised(module); \
+ X(); \
+ Py_RETURN_NONE; \
+}
/*********************************************************************
Global Functions
@@ -3617,8 +3708,12 @@ _curses_initscr_impl(PyObject *module)
WINDOW *win;
if (curses_initscr_called) {
- wrefresh(stdscr);
cursesmodule_state *state = get_cursesmodule_state(module);
+ int code = wrefresh(stdscr);
+ if (code == ERR) {
+ _curses_set_null_error(state, "wrefresh", "initscr");
+ return NULL;
+ }
return PyCursesWindow_New(state, stdscr, NULL, NULL);
}
@@ -4802,7 +4897,12 @@ _curses_unctrl(PyObject *module, PyObject *ch)
if (!PyCurses_ConvertToChtype(NULL, ch, &ch_))
return NULL;
- return PyBytes_FromString(unctrl(ch_));
+ const char *res = unctrl(ch_);
+ if (res == NULL) {
+ curses_set_null_error(module, "unctrl", NULL);
+ return NULL;
+ }
+ return PyBytes_FromString(res);
}
/*[clinic input]
@@ -4971,13 +5071,7 @@ _curses_assume_default_colors_impl(PyObject *module, int
fg, int bg)
PyCursesStatefulInitialisedColor(module);
code = assume_default_colors(fg, bg);
- if (code != ERR) {
- Py_RETURN_NONE;
- } else {
- cursesmodule_state *state = get_cursesmodule_state(module);
- PyErr_SetString(state->error, "assume_default_colors() returned ERR");
- return NULL;
- }
+ return curses_check_err(module, code, "assume_default_colors", NULL);
}
#endif /* STRICT_SYSV_CURSES */
diff --git a/Modules/clinic/_cursesmodule.c.h b/Modules/clinic/_cursesmodule.c.h
index a898a7e17cf8d1..49c864318c8aa8 100644
--- a/Modules/clinic/_cursesmodule.c.h
+++ b/Modules/clinic/_cursesmodule.c.h
@@ -733,23 +733,13 @@ PyDoc_STRVAR(_curses_window_getbkgd__doc__,
#define _CURSES_WINDOW_GETBKGD_METHODDEF \
{"getbkgd", (PyCFunction)_curses_window_getbkgd, METH_NOARGS,
_curses_window_getbkgd__doc__},
-static long
+static PyObject *
_curses_window_getbkgd_impl(PyCursesWindowObject *self);
static PyObject *
_curses_window_getbkgd(PyObject *self, PyObject *Py_UNUSED(ignored))
{
- PyObject *return_value = NULL;
- long _return_value;
-
- _return_value = _curses_window_getbkgd_impl((PyCursesWindowObject *)self);
- if ((_return_value == -1) && PyErr_Occurred()) {
- goto exit;
- }
- return_value = PyLong_FromLong(_return_value);
-
-exit:
- return return_value;
+ return _curses_window_getbkgd_impl((PyCursesWindowObject *)self);
}
PyDoc_STRVAR(_curses_window_getch__doc__,
@@ -1050,7 +1040,7 @@ PyDoc_STRVAR(_curses_window_inch__doc__,
#define _CURSES_WINDOW_INCH_METHODDEF \
{"inch", (PyCFunction)_curses_window_inch, METH_VARARGS,
_curses_window_inch__doc__},
-static unsigned long
+static PyObject *
_curses_window_inch_impl(PyCursesWindowObject *self, int group_right_1,
int y, int x);
@@ -1061,7 +1051,6 @@ _curses_window_inch(PyObject *self, PyObject *args)
int group_right_1 = 0;
int y = 0;
int x = 0;
- unsigned long _return_value;
switch (PyTuple_GET_SIZE(args)) {
case 0:
@@ -1076,11 +1065,7 @@ _curses_window_inch(PyObject *self, PyObject *args)
PyErr_SetString(PyExc_TypeError, "_curses.window.inch requires 0
to 2 arguments");
goto exit;
}
- _return_value = _curses_window_inch_impl((PyCursesWindowObject *)self,
group_right_1, y, x);
- if ((_return_value == (unsigned long)-1) && PyErr_Occurred()) {
- goto exit;
- }
- return_value = PyLong_FromUnsignedLong(_return_value);
+ return_value = _curses_window_inch_impl((PyCursesWindowObject *)self,
group_right_1, y, x);
exit:
return return_value;
@@ -4435,4 +4420,4 @@ _curses_has_extended_color_support(PyObject *module,
PyObject *Py_UNUSED(ignored
#ifndef _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
#define _CURSES_ASSUME_DEFAULT_COLORS_METHODDEF
#endif /* !defined(_CURSES_ASSUME_DEFAULT_COLORS_METHODDEF) */
-/*[clinic end generated code: output=7753612d7613903c input=a9049054013a1b77]*/
+/*[clinic end generated code: output=a083473003179b30 input=a9049054013a1b77]*/
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]