]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-134209: use heap-allocated memory in `_curses.window.{instr,getstr}` (GH-134283)
authortigerding <43339228+zydtiger@users.noreply.github.com>
Tue, 20 May 2025 20:36:04 +0000 (16:36 -0400)
committerGitHub <noreply@github.com>
Tue, 20 May 2025 20:36:04 +0000 (20:36 +0000)
* made curses buffer heap allocated instead of stack
* change docs to explicitly mention the max buffer size
* changing GetStr() function to behave similarly too
* Update Doc/library/curses.rst
* Update instr with proper return error handling
* Update Modules/_cursesmodule.c
* change to strlen and better memory safety
* change from const int to Py_ssize_t
* add mem allocation guard
* update versionchanged to mention it was an increase.
* explicitly use versionchanged 3.14 as that is its own branch now.

TESTED: `python -m test -u curses test_curses`

---------

Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Doc/library/curses.rst
Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst [new file with mode: 0644]
Modules/_cursesmodule.c

index 137504c51b43588928d37546cd805d64735b5989..5ec23b61396773a9d688d5bdfe0e130379aacb58 100644 (file)
@@ -988,6 +988,10 @@ the following methods and attributes:
             window.getstr(y, x, n)
 
    Read a bytes object from the user, with primitive line editing capacity.
+   The maximum value for *n* is 2047.
+
+   .. versionchanged:: 3.14
+      The maximum value for *n* was increased from 1023 to 2047.
 
 
 .. method:: window.getyx()
@@ -1079,6 +1083,10 @@ the following methods and attributes:
    current cursor position, or at *y*, *x* if specified. Attributes are stripped
    from the characters.  If *n* is specified, :meth:`instr` returns a string
    at most *n* characters long (exclusive of the trailing NUL).
+   The maximum value for *n* is 2047.
+
+   .. versionchanged:: 3.14
+      The maximum value for *n* was increased from 1023 to 2047.
 
 
 .. method:: window.is_linetouched(line)
diff --git a/Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst b/Misc/NEWS.d/next/Library/2025-05-19-20-59-06.gh-issue-134209.anhTcF.rst
new file mode 100644 (file)
index 0000000..f985872
--- /dev/null
@@ -0,0 +1,3 @@
+:mod:`curses`: The :meth:`curses.window.instr` and :meth:`curses.window.getstr`
+methods now allocate their internal buffer on the heap instead of the stack;
+in addition, the max buffer size is increased from 1023 to 2047.
index 2e6ec822e2d5b6785e96d5b66ff216467deb5417..5e1eccee3e4a89ea9256856406961f158d503f7d 100644 (file)
@@ -1816,7 +1816,7 @@ _curses.window.getstr
     x: int
         X-coordinate.
     ]
-    n: int = 1023
+    n: int = 2047
         Maximal number of characters.
     /
 
@@ -1829,62 +1829,80 @@ PyCursesWindow_GetStr(PyObject *op, PyObject *args)
     PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
 
     int x, y, n;
-    char rtn[1024]; /* This should be big enough.. I hope */
-    int rtn2;
+    int rtn;
+
+    /* could make the buffer size larger/dynamic */
+    Py_ssize_t max_buf_size = 2048;
+    PyObject *result = PyBytes_FromStringAndSize(NULL, max_buf_size);
+    if (result == NULL)
+        return NULL;
+    char *buf = PyBytes_AS_STRING(result);
 
     switch (PyTuple_Size(args)) {
     case 0:
         Py_BEGIN_ALLOW_THREADS
-        rtn2 = wgetnstr(self->win,rtn, 1023);
+        rtn = wgetnstr(self->win, buf, max_buf_size - 1);
         Py_END_ALLOW_THREADS
         break;
     case 1:
         if (!PyArg_ParseTuple(args,"i;n", &n))
-            return NULL;
+            goto error;
         if (n < 0) {
             PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
-            return NULL;
+            goto error;
         }
         Py_BEGIN_ALLOW_THREADS
-        rtn2 = wgetnstr(self->win, rtn, Py_MIN(n, 1023));
+        rtn = wgetnstr(self->win, buf, Py_MIN(n, max_buf_size - 1));
         Py_END_ALLOW_THREADS
         break;
     case 2:
         if (!PyArg_ParseTuple(args,"ii;y,x",&y,&x))
-            return NULL;
+            goto error;
         Py_BEGIN_ALLOW_THREADS
 #ifdef STRICT_SYSV_CURSES
-        rtn2 = wmove(self->win,y,x)==ERR ? ERR : wgetnstr(self->win, rtn, 1023);
+        rtn = wmove(self->win,y,x)==ERR ? ERR : wgetnstr(self->win, rtn, max_buf_size - 1);
 #else
-        rtn2 = mvwgetnstr(self->win,y,x,rtn, 1023);
+        rtn = mvwgetnstr(self->win,y,x,buf, max_buf_size - 1);
 #endif
         Py_END_ALLOW_THREADS
         break;
     case 3:
         if (!PyArg_ParseTuple(args,"iii;y,x,n", &y, &x, &n))
-            return NULL;
+            goto error;
         if (n < 0) {
             PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
-            return NULL;
+            goto error;
         }
 #ifdef STRICT_SYSV_CURSES
         Py_BEGIN_ALLOW_THREADS
-        rtn2 = wmove(self->win,y,x)==ERR ? ERR :
-        wgetnstr(self->win, rtn, Py_MIN(n, 1023));
+        rtn = wmove(self->win,y,x)==ERR ? ERR :
+        wgetnstr(self->win, rtn, Py_MIN(n, max_buf_size - 1));
         Py_END_ALLOW_THREADS
 #else
         Py_BEGIN_ALLOW_THREADS
-        rtn2 = mvwgetnstr(self->win, y, x, rtn, Py_MIN(n, 1023));
+        rtn = mvwgetnstr(self->win, y, x, buf, Py_MIN(n, max_buf_size - 1));
         Py_END_ALLOW_THREADS
 #endif
         break;
     default:
         PyErr_SetString(PyExc_TypeError, "getstr requires 0 to 3 arguments");
+        goto error;
+    }
+
+    if (rtn == ERR) {
+        Py_DECREF(result);
+        return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+    }
+
+    if (_PyBytes_Resize(&result, strlen(buf)) < 0) {
         return NULL;
     }
-    if (rtn2 == ERR)
-        rtn[0] = 0;
-    return PyBytes_FromString(rtn);
+
+    return result;
+
+error:
+    Py_DECREF(result);
+    return NULL;
 }
 
 /*[clinic input]
@@ -2023,7 +2041,7 @@ _curses.window.instr
     x: int
         X-coordinate.
     ]
-    n: int = 1023
+    n: int = 2047
         Maximal number of characters.
     /
 
@@ -2040,43 +2058,61 @@ PyCursesWindow_InStr(PyObject *op, PyObject *args)
     PyCursesWindowObject *self = _PyCursesWindowObject_CAST(op);
 
     int x, y, n;
-    char rtn[1024]; /* This should be big enough.. I hope */
-    int rtn2;
+    int rtn;
+
+    /* could make the buffer size larger/dynamic */
+    Py_ssize_t max_buf_size = 2048;
+    PyObject *result = PyBytes_FromStringAndSize(NULL, max_buf_size);
+    if (result == NULL)
+        return NULL;
+    char *buf = PyBytes_AS_STRING(result);
 
     switch (PyTuple_Size(args)) {
     case 0:
-        rtn2 = winnstr(self->win,rtn, 1023);
+        rtn = winnstr(self->win, buf, max_buf_size - 1);
         break;
     case 1:
         if (!PyArg_ParseTuple(args,"i;n", &n))
-            return NULL;
+            goto error;
         if (n < 0) {
             PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
-            return NULL;
+            goto error;
         }
-        rtn2 = winnstr(self->win, rtn, Py_MIN(n, 1023));
+        rtn = winnstr(self->win, buf, Py_MIN(n, max_buf_size - 1));
         break;
     case 2:
         if (!PyArg_ParseTuple(args,"ii;y,x",&y,&x))
-            return NULL;
-        rtn2 = mvwinnstr(self->win,y,x,rtn,1023);
+            goto error;
+        rtn = mvwinnstr(self->win, y, x, buf, max_buf_size - 1);
         break;
     case 3:
         if (!PyArg_ParseTuple(args, "iii;y,x,n", &y, &x, &n))
-            return NULL;
+            goto error;
         if (n < 0) {
             PyErr_SetString(PyExc_ValueError, "'n' must be nonnegative");
-            return NULL;
+            goto error;
         }
-        rtn2 = mvwinnstr(self->win, y, x, rtn, Py_MIN(n,1023));
+        rtn = mvwinnstr(self->win, y, x, buf, Py_MIN(n, max_buf_size - 1));
         break;
     default:
         PyErr_SetString(PyExc_TypeError, "instr requires 0 or 3 arguments");
+        goto error;
+    }
+
+    if (rtn == ERR) {
+        Py_DECREF(result);
+        return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
+    }
+
+    if (_PyBytes_Resize(&result, strlen(buf)) < 0) {
         return NULL;
     }
-    if (rtn2 == ERR)
-        rtn[0] = 0;
-    return PyBytes_FromString(rtn);
+
+    return result;
+
+error:
+    Py_DECREF(result);
+    return NULL;
 }
 
 /*[clinic input]