Repository navigation
curses: buffered reads assume ncurses' reading of the size argument #154855
Description
Bug report
window.instr(), in_wstr(), in_wchstr(), getstr() and get_wstr() pass n to the library and allocate n + 1 elements for the result. That encodes ncurses' reading of n: it stores n characters and adds a terminator, writing n + 1 elements in all.
X/Open does not say that. For innstr() it says the functions "store at most n bytes in the string pointed to by str", for innwstr() "at most n characters in the array", for in_wchnstr() "at most n cchar_t elements" -- and none of the three mentions a terminating null at all. NetBSD curses reads that limit as covering the terminator, so it stores n - 1 characters, and every one of these methods returns one character too few there. Window containing "abcde", n = 5: ncurses gives "abcde", NetBSD gives "abcd".
Both readings are defensible, and NetBSD documents its own (curses_inch.3: read "until either n - 1 characters are read"). The problem is ours: we ask for n and assume ncurses' answer.
Asking a library that counts the terminator for n + 1 gives the n characters ncurses returns:
#if defined(NCURSES_VERSION) || defined(PDCURSES)
# define CURSES_STR_EXTRA 0
#else
# define CURSES_STR_EXTRA 1
#endifPDCurses follows ncurses (measured), so only the unknown case is bumped, and the buffers become n + 1 + CURSES_STR_EXTRA so that such a library cannot overrun them. Note that the extra element does not always add exactly one character: X/Open requires these functions to store only whole characters ("If the array is not large enough to contain any complete characters, the function fails"), so with multi-byte or combining characters the last cell is admitted as a whole or not at all.
test_curses against NetBSD native curses goes from 34 to 22 failures on main; builds against ncurses are unchanged.
The strlen() side of this is gh-154781: in_wstr() scans for a terminator that winnwstr() need not have written. X/Open supports the fix suggested there -- it promises no terminator for the innstr() family and specifies that these functions return the number of characters actually read, so the return value is the length. The same holds for instr(), which gh-154781 reports as unaffected: it works only because both libraries happen to terminate. getstr() and get_wstr() are the opposite case -- getstr() "is then terminated with a null byte", while getnstr() returns OK, not a count -- so they must keep scanning for the terminator.
Linked PRs
- gh-154855: Ask non-ncurses curses for one more character #154870
- [3.15] gh-154855: Ask non-ncurses curses for one more character (GH-154870) #156283
- [3.14] gh-154855: Ask non-ncurses curses for one more character (GH-154870) #156284
- [3.13] gh-154855: Ask non-ncurses curses for one more character (GH-154870) #156285
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
gh-154855: Ask non-ncurses curses for one more character (GH-154870)
[3.13] gh-154855: Ask non-ncurses curses for one more character (GH-1…
[3.14] gh-154855: Ask non-ncurses curses for one more character (GH-1…
pythongh-154855: Ask non-ncurses curses for one more character (pytho…
[3.15] gh-154855: Ask non-ncurses curses for one more character (GH-1…