Skip to content

Commit b589445

Browse files
committed
gh-155974: Restore the window attributes when a curses write fails
addstr(), addnstr(), insstr() and insnstr() set the window rendition to the caller's attr, write, then restore the previous rendition. Since 30dde1e the restore sits below an early return taken when the write fails, so a failed write leaves the caller's attr on the window and drops whatever the application had set with attrset(). Restore the rendition first and report the write error afterwards. A wattrset() failure is still reported when the write itself succeeded.
1 parent fa0ec86 commit b589445

3 files changed

Lines changed: 43 additions & 28 deletions

File tree

Lib/test/test_curses.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,21 @@ def test_output_string_embedded_null_chars(self):
895895
self.assertRaises(ValueError, stdscr.insstr, arg)
896896
self.assertRaises(ValueError, stdscr.insnstr, arg, 1)
897897

898+
def test_output_string_attr_restored(self):
899+
# A write with an attr restores the window rendition afterwards,
900+
# whether it succeeded or failed.
901+
win = curses.newwin(2, 10, 0, 0)
902+
for func, args in [(win.addstr, ('x',)), (win.addnstr, ('x', 1)),
903+
(win.insstr, ('x',)), (win.insnstr, ('x', 1))]:
904+
with self.subTest(func.__qualname__):
905+
win.attrset(curses.A_UNDERLINE)
906+
# y=100 is outside the window, so the write fails.
907+
self.assertRaises(curses.error, func, 100, 0, *args,
908+
curses.A_BOLD)
909+
self.assertEqual(win.getattrs(), curses.A_UNDERLINE)
910+
func(0, 0, *args, curses.A_BOLD)
911+
self.assertEqual(win.getattrs(), curses.A_UNDERLINE)
912+
898913
def test_add_string_behavior(self):
899914
# addstr() advances the cursor past the written text; addnstr()
900915
# writes at most n characters.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a regression in Python 3.15: :meth:`~curses.window.addstr`,
2+
:meth:`~curses.window.addnstr`, :meth:`~curses.window.insstr` and
3+
:meth:`~curses.window.insnstr` again restore the window attributes when the
4+
write fails, instead of leaving the temporary *attr* applied.

Modules/_cursesmodule.c

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,15 +2262,14 @@ _curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1,
22622262
}
22632263
Py_DECREF(bytesobj);
22642264
}
2265-
if (rtn == ERR) {
2266-
curses_window_set_error(self, funcname, "addstr");
2267-
return NULL;
2268-
}
22692265
if (use_attr) {
2270-
rtn = wattrset(self->win, attr_old);
2271-
return curses_window_check_err(self, rtn, "wattrset", "addstr");
2266+
int attr_rtn = wattrset(self->win, attr_old);
2267+
if (rtn != ERR) {
2268+
rtn = attr_rtn;
2269+
funcname = "wattrset";
2270+
}
22722271
}
2273-
Py_RETURN_NONE;
2272+
return curses_window_check_err(self, rtn, funcname, "addstr");
22742273
}
22752274

22762275
/*[clinic input]
@@ -2373,15 +2372,14 @@ _curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1,
23732372
}
23742373
Py_DECREF(bytesobj);
23752374
}
2376-
if (rtn == ERR) {
2377-
curses_window_set_error(self, funcname, "addnstr");
2378-
return NULL;
2379-
}
23802375
if (use_attr) {
2381-
rtn = wattrset(self->win, attr_old);
2382-
return curses_window_check_err(self, rtn, "wattrset", "addnstr");
2376+
int attr_rtn = wattrset(self->win, attr_old);
2377+
if (rtn != ERR) {
2378+
rtn = attr_rtn;
2379+
funcname = "wattrset";
2380+
}
23832381
}
2384-
Py_RETURN_NONE;
2382+
return curses_window_check_err(self, rtn, funcname, "addnstr");
23852383
}
23862384

23872385
/*[clinic input]
@@ -4094,15 +4092,14 @@ _curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1,
40944092
}
40954093
Py_DECREF(bytesobj);
40964094
}
4097-
if (rtn == ERR) {
4098-
curses_window_set_error(self, funcname, "insstr");
4099-
return NULL;
4100-
}
41014095
if (use_attr) {
4102-
rtn = wattrset(self->win, attr_old);
4103-
return curses_window_check_err(self, rtn, "wattrset", "insstr");
4096+
int attr_rtn = wattrset(self->win, attr_old);
4097+
if (rtn != ERR) {
4098+
rtn = attr_rtn;
4099+
funcname = "wattrset";
4100+
}
41044101
}
4105-
Py_RETURN_NONE;
4102+
return curses_window_check_err(self, rtn, funcname, "insstr");
41064103
}
41074104

41084105
/*[clinic input]
@@ -4206,15 +4203,14 @@ _curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
42064203
}
42074204
Py_DECREF(bytesobj);
42084205
}
4209-
if (rtn == ERR) {
4210-
curses_window_set_error(self, funcname, "insnstr");
4211-
return NULL;
4212-
}
42134206
if (use_attr) {
4214-
rtn = wattrset(self->win, attr_old);
4215-
return curses_window_check_err(self, rtn, "wattrset", "insnstr");
4207+
int attr_rtn = wattrset(self->win, attr_old);
4208+
if (rtn != ERR) {
4209+
rtn = attr_rtn;
4210+
funcname = "wattrset";
4211+
}
42164212
}
4217-
Py_RETURN_NONE;
4213+
return curses_window_check_err(self, rtn, funcname, "insnstr");
42184214
}
42194215

42204216
/*[clinic input]

0 commit comments

Comments
 (0)