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 ee36db5500 for similar
changes.
This commit is contained in:
Bénédikt Tran 2025-05-27 12:15:16 +02:00 committed by GitHub
parent 604f83550b
commit 30dde1eeb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 192 additions and 93 deletions

View file

@ -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
================

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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 */

View file

@ -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]*/