Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Doc/library/curses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,13 @@ The module :mod:`!curses` defines the following functions:
.. function:: color_pair(pair_number)

Return the attribute value for displaying text in the specified color pair.
Only the first 256 color pairs are supported. This
attribute value can be combined with :const:`A_STANDOUT`, :const:`A_REVERSE`,
and the other :const:`!A_\*` attributes. :func:`pair_number` is the counterpart
to this function.
Only color pairs that fit in the color-pair field of the returned value can
be represented (usually the first 256); a larger *pair_number* raises
:exc:`OverflowError` rather than being silently masked to a different pair.
Use :meth:`~window.color_set` or :meth:`~window.attr_set` to display higher
pairs. This attribute value can be combined with :const:`A_STANDOUT`,
:const:`A_REVERSE`, and the other :const:`!A_\*` attributes.
:func:`pair_number` is the counterpart to this function.


.. function:: curs_set(visibility)
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,10 @@ def test_argument_errors(self):
# A character argument must be an int, a byte or a one-element string.
self.assertRaises(TypeError, win.addch, [])
self.assertRaises(OverflowError, win.addch, 2**64)
# The attribute argument is rejected, not truncated, when out of range.
self.assertRaises(OverflowError, win.addch, 'a', 2**64)
self.assertRaises(OverflowError, win.addstr, 'a', 2**64)
self.assertRaises(TypeError, win.addch, 'a', 'bold')
# A string method rejects a non-string, non-bytes argument.
self.assertRaises(TypeError, win.addstr, 5)
self.assertRaises(TypeError, win.addstr)
Expand Down Expand Up @@ -969,6 +973,11 @@ def test_attributes(self):
self.assertRaises(OverflowError, win.attr_set, -1)
self.assertRaises(OverflowError, win.attr_on, -1)
self.assertRaises(OverflowError, win.attr_set, 1 << 64)
# attron()/attroff()/attrset() reject a bad attribute too.
self.assertRaises(OverflowError, win.attron, 1 << 64)
self.assertRaises(OverflowError, win.attroff, -1)
self.assertRaises(OverflowError, win.attrset, 1 << 64)
self.assertRaises(TypeError, win.attron, 'x')

@requires_colors
def test_attr_color_pair(self):
Expand Down Expand Up @@ -1717,6 +1726,11 @@ def test_color_attrs(self):
self.assertEqual(curses.pair_number(attr | curses.A_BOLD), pair)
self.assertEqual(curses.color_pair(0), 0)
self.assertEqual(curses.pair_number(0), 0)
# A pair too large to fit is rejected, not silently masked (gh-119138).
max_pair = curses.pair_number(curses.A_COLOR)
self.assertEqual(curses.pair_number(curses.color_pair(max_pair)), max_pair)
self.assertRaises(OverflowError, curses.color_pair, max_pair + 1)
self.assertRaises(OverflowError, curses.color_pair, -1)

@requires_curses_func('use_default_colors')
@requires_colors
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The :mod:`curses` module now raises :exc:`OverflowError` instead of silently
truncating an out-of-range value: :func:`curses.color_pair` rejects a color
pair number that does not fit in the ``chtype`` color field, and the
``attr`` argument of the character-cell and attribute methods
(:meth:`~curses.window.addch`, :meth:`~curses.window.addstr`,
:meth:`~curses.window.attron`, :meth:`~curses.window.attrset` and others) is
checked against the ``chtype`` range.
106 changes: 60 additions & 46 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ typedef struct {
obj may also be a complexchar, whose cell is used directly; it carries its
own rendition, so supplying *attr* too (attr_given) is rejected. */
static int
PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, long attr,
PyCurses_ConvertToCell(PyCursesWindowObject *win, PyObject *obj, attr_t attr,
int attr_given, const char *funcname,
chtype *pch, cchar_t *pwc)
{
Expand Down Expand Up @@ -931,8 +931,9 @@ attr_converter(PyObject *arg, void *ptr)
class attr_converter(CConverter):
type = 'attr_t'
converter = 'attr_converter'
c_ignored_default = '0'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=6132d3d99d3ec25a]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=57b994c97cbd5e80]*/

#ifdef HAVE_NCURSESW
/* -------------------------------------------------------*/
Expand Down Expand Up @@ -1835,7 +1836,7 @@ _curses.window.addch
Character to add.

[
attr: long
attr: attr
Attributes for the character.
]
/
Expand All @@ -1851,8 +1852,8 @@ current settings for the window object.
static PyObject *
_curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *ch, int group_right_1,
long attr)
/*[clinic end generated code: output=00f4c37af3378f45 input=ab196a1dac3d354c]*/
attr_t attr)
/*[clinic end generated code: output=3306e15a7059998f input=0a09ecdd04aa0a2d]*/
{
int coordinates_group = group_left_1;
int rtn;
Expand Down Expand Up @@ -1908,7 +1909,7 @@ _curses_window_addch_impl(PyCursesWindowObject *self, int group_left_1,
#endif

static int
curses_wattrset(PyCursesWindowObject *self, long attr, const char *funcname)
curses_wattrset(PyCursesWindowObject *self, attr_t attr, const char *funcname)
{
if (wattrset(self->win, attr) == ERR) {
curses_window_set_error(self, "wattrset", funcname);
Expand All @@ -1931,7 +1932,7 @@ _curses.window.addstr
String to add.

[
attr: long
attr: attr
Attributes for characters.
]
/
Expand All @@ -1947,8 +1948,8 @@ current settings for the window object.
static PyObject *
_curses_window_addstr_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *str, int group_right_1,
long attr)
/*[clinic end generated code: output=65a928ea85ff3115 input=ff6cbb91448a22a3]*/
attr_t attr)
/*[clinic end generated code: output=4942cdb202012076 input=0202b09895bcb472]*/
{
int rtn;
int strtype;
Expand Down Expand Up @@ -2046,7 +2047,7 @@ _curses.window.addnstr
Maximal number of characters.

[
attr: long
attr: attr
Attributes for characters.
]
/
Expand All @@ -2062,8 +2063,8 @@ current settings for the window object.
static PyObject *
_curses_window_addnstr_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *str, int n,
int group_right_1, long attr)
/*[clinic end generated code: output=6d21cee2ce6876d9 input=72718415c2744a2a]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=356ce38504dabf1d input=147405505606cd08]*/
{
int rtn;
int strtype;
Expand Down Expand Up @@ -2146,7 +2147,7 @@ _curses.window.bkgd
ch: object
Background character.
[
attr: long
attr: attr
Background attributes.
]
/
Expand All @@ -2156,8 +2157,8 @@ Set the background property of the window.

static PyObject *
_curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch,
int group_right_1, long attr)
/*[clinic end generated code: output=73cb11ecca59612f input=a2129c1b709db432]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=4dc2599da3afa46a input=7aee8008ff8066a5]*/
{
chtype bkgd;
#ifdef HAVE_NCURSESW
Expand All @@ -2183,15 +2184,15 @@ _curses_window_bkgd_impl(PyCursesWindowObject *self, PyObject *ch,
/*[clinic input]
_curses.window.attroff

attr: long
attr: attr
/

Remove attribute attr from the "background" set.
[clinic start generated code]*/

static PyObject *
_curses_window_attroff_impl(PyCursesWindowObject *self, long attr)
/*[clinic end generated code: output=8a2fcd4df682fc64 input=786beedf06a7befe]*/
_curses_window_attroff_impl(PyCursesWindowObject *self, attr_t attr)
/*[clinic end generated code: output=27c9e77df32fa5d3 input=a22d4035e962e9a7]*/
{
int rtn = wattroff(self->win, (attr_t)attr);
return curses_window_check_err(self, rtn, "wattroff", "attroff");
Expand All @@ -2200,15 +2201,15 @@ _curses_window_attroff_impl(PyCursesWindowObject *self, long attr)
/*[clinic input]
_curses.window.attron

attr: long
attr: attr
/

Add attribute attr to the "background" set.
[clinic start generated code]*/

static PyObject *
_curses_window_attron_impl(PyCursesWindowObject *self, long attr)
/*[clinic end generated code: output=7afea43b237fa870 input=b57f824e1bf58326]*/
_curses_window_attron_impl(PyCursesWindowObject *self, attr_t attr)
/*[clinic end generated code: output=150ff7c387068cc7 input=361b6389f4d08681]*/
{
int rtn = wattron(self->win, (attr_t)attr);
return curses_window_check_err(self, rtn, "wattron", "attron");
Expand All @@ -2217,15 +2218,15 @@ _curses_window_attron_impl(PyCursesWindowObject *self, long attr)
/*[clinic input]
_curses.window.attrset

attr: long
attr: attr
/

Set the "background" set of attributes.
[clinic start generated code]*/

static PyObject *
_curses_window_attrset_impl(PyCursesWindowObject *self, long attr)
/*[clinic end generated code: output=84e379bff20c0433 input=42e400c0d0154ab5]*/
_curses_window_attrset_impl(PyCursesWindowObject *self, attr_t attr)
/*[clinic end generated code: output=1b57b2a512603eb0 input=af748b1c18e35c34]*/
{
int rtn = wattrset(self->win, (attr_t)attr);
return curses_window_check_err(self, rtn, "wattrset", "attrset");
Expand Down Expand Up @@ -2356,7 +2357,7 @@ _curses.window.bkgdset
ch: object
Background character.
[
attr: long
attr: attr
Background attributes.
]
/
Expand All @@ -2366,8 +2367,8 @@ Set the window's background.

static PyObject *
_curses_window_bkgdset_impl(PyCursesWindowObject *self, PyObject *ch,
int group_right_1, long attr)
/*[clinic end generated code: output=3c32f2de5685a482 input=1f0811b24af821ca]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=32f5117c9e45422a input=64cf7cd3562b379b]*/
{
chtype bkgd;
#ifdef HAVE_NCURSESW
Expand Down Expand Up @@ -2756,7 +2757,7 @@ _curses.window.echochar
Character to add.

[
attr: long
attr: attr
Attributes for the character.
]
/
Expand All @@ -2766,8 +2767,8 @@ Add character ch with attribute attr, and refresh.

static PyObject *
_curses_window_echochar_impl(PyCursesWindowObject *self, PyObject *ch,
int group_right_1, long attr)
/*[clinic end generated code: output=f42da9e200c935e5 input=26e16855ec1b0e78]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=ab03afa580aa6a2a input=cd74c42aadcc7e30]*/
{
chtype ch_;
#ifdef HAVE_NCURSESW
Expand Down Expand Up @@ -3194,7 +3195,7 @@ _curses.window.hline
Line length.

[
attr: long
attr: attr
Attributes for the characters.
]
/
Expand All @@ -3205,8 +3206,8 @@ Display a horizontal line.
static PyObject *
_curses_window_hline_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *ch, int n,
int group_right_1, long attr)
/*[clinic end generated code: output=c00d489d61fc9eef input=924f8c28521bc2ec]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=2c7489b8bd10c446 input=5d9f72ccba73975c]*/
{
chtype ch_;
#ifdef HAVE_NCURSESW
Expand Down Expand Up @@ -3250,7 +3251,7 @@ _curses.window.insch
Character to insert.

[
attr: long
attr: attr
Attributes for the character.
]
/
Expand All @@ -3264,8 +3265,8 @@ right, with the rightmost characters on the line being lost.
static PyObject *
_curses_window_insch_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *ch, int group_right_1,
long attr)
/*[clinic end generated code: output=ade8cfe3a3bf3e34 input=47d2989159ae6ca7]*/
attr_t attr)
/*[clinic end generated code: output=9d2576c0d8d982c4 input=f76641d529dbd8af]*/
{
int rtn;
chtype ch_ = 0;
Expand Down Expand Up @@ -3598,7 +3599,7 @@ _curses.window.insstr
String to insert.

[
attr: long
attr: attr
Attributes for characters.
]
/
Expand All @@ -3615,8 +3616,8 @@ moving to y, x, if specified).
static PyObject *
_curses_window_insstr_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *str, int group_right_1,
long attr)
/*[clinic end generated code: output=c259a5265ad0b777 input=dbfbdd3892155ea6]*/
attr_t attr)
/*[clinic end generated code: output=2c8ed843880619ab input=f4a9d26b270058c2]*/
{
int rtn;
int strtype;
Expand Down Expand Up @@ -3710,7 +3711,7 @@ _curses.window.insnstr
Maximal number of characters.

[
attr: long
attr: attr
Attributes for characters.
]
/
Expand All @@ -3728,8 +3729,8 @@ does not change (after moving to y, x, if specified).
static PyObject *
_curses_window_insnstr_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *str, int n,
int group_right_1, long attr)
/*[clinic end generated code: output=971a32ea6328ec8b input=fd0a9b65b84b385f]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=4895829689f3bdd2 input=7412feb3910276bf]*/
{
int rtn;
int strtype;
Expand Down Expand Up @@ -4290,7 +4291,7 @@ _curses.window.vline
Line length.

[
attr: long
attr: attr
Attributes for the character.
]
/
Expand All @@ -4301,8 +4302,8 @@ Display a vertical line.
static PyObject *
_curses_window_vline_impl(PyCursesWindowObject *self, int group_left_1,
int y, int x, PyObject *ch, int n,
int group_right_1, long attr)
/*[clinic end generated code: output=287ad1cc8982217f input=1d4aa27ff0309bbc]*/
int group_right_1, attr_t attr)
/*[clinic end generated code: output=18efd3ea37bb04f6 input=e8678752623197a1]*/
{
chtype ch_;
#ifdef HAVE_NCURSESW
Expand Down Expand Up @@ -5272,7 +5273,20 @@ _curses_color_pair_impl(PyObject *module, int pair_number)
PyCursesStatefulInitialised(module);
PyCursesStatefulInitialisedColor(module);

return PyLong_FromLong(COLOR_PAIR(pair_number));
/* COLOR_PAIR() packs the pair into a limited field; a pair too large to be
recovered by its inverse PAIR_NUMBER() would be masked to a different
one. Reject pairs that do not round-trip (this assumes only that the two
macros are inverses). color_set()/attr_set()/complexchar can still
display larger pairs. */
chtype attr = COLOR_PAIR(pair_number);
if (pair_number < 0 || PAIR_NUMBER(attr) != pair_number) {
PyErr_Format(PyExc_OverflowError,
"color pair %d does not fit in a chtype "
"(color_pair() can encode only pairs 0 to %d)",
pair_number, (int)PAIR_NUMBER(A_COLOR));
return NULL;
}
return PyLong_FromLong(attr);
}

/*[clinic input]
Expand Down
Loading
Loading