ncurses 6.6.20251231, wide-character build (libncursesw), Linux/glibc.
(The code path is present in every wide build I checked, ncurses 5.9
through 6.6.)
On a wide-character build, inserting a printable 8-bit byte > 127 with
winsch()/insch() stores the byte as a code point directly, without
converting it
through the locale, whereas waddch() with the same byte runs the normal
mbtowc
conversion. So under a non-Latin-1 8-bit locale addch() decodes the
byte and
insch() does not.
Under en_US.ISO-8859-15, byte 0xA4 is EURO SIGN (U+20AC): addch(0xA4)
stores the
euro, but insch(0xA4) stores U+00A4 instead -- a different character. For a
Latin-1 locale the bug is invisible, since there the byte equals the
code point
(0xA4 -> U+00A4 either way), which is presumably why it has gone unnoticed.
#include <locale.h>
#include <curses.h>
#include <stdio.h>
int main(void) {
char a[4] = {0}, b[4] = {0};
setlocale(LC_ALL, "");
initscr();
addch(0xA4); /* addch decodes the byte via the
locale */
mvinsch(1, 0, 0xA4); /* insch under test */
mvwinnstr(stdscr, 0, 0, a, 1);
mvwinnstr(stdscr, 1, 0, b, 1);
endwin();
fprintf(stderr, "addch(0xA4): winnstr=0x%02X\n", (unsigned
char)a[0]);
fprintf(stderr, "insch(0xA4): winnstr=0x%02X\n", (unsigned
char)b[0]);
return 0;
}
$ cc repro.c -o repro -lncursesw
$ TERM=xterm LC_ALL=en_US.ISO-8859-15 ./repro
winnstr() reads each cell back as a locale byte. Expected, with insch
matching
addch, is 0xA4 for both. Actual:
addch(0xA4): winnstr=0xA4 (the euro, read back correctly)
insch(0xA4): winnstr=0x20 (a blank: U+00A4 is not in
ISO-8859-15, so
the inserted euro was lost)
(Use addch(0xA4), not addstr("euro"): a UTF-8 source literal is misread
under an
8-bit locale.)
In ncurses/base/lib_insch.c, _nc_insert_ch() handles a printable 8-bit
byte with
a fast path that builds the cell straight from the byte:
if (... (isprint(ch8) || ...)) {
...
SetChar2(wch, ch); /* byte used directly as the code
point */
}
The locale conversion, _nc_build_wch() (mbtowc), is only reached in the
trailing
"else" branch, for a byte that is not isprint() in the current locale.
waddch()/wadd_wch() route every non-ASCII byte through _nc_build_wch(), so
addch() decodes 0xA4 to U+20AC while insch() short-circuits to U+00A4.
A fix
would let the wide build take the _nc_build_wch() path for an 8-bit byte
in the
insert case too (the isprint() fast path is fine for ASCII).
This is the write-side companion to the winch()/inch() locale issue reported
separately: there winnstr() decodes a cell via the locale while winch()
returns
the truncated code point. Both come from the legacy 8-bit chtype API
treating a
byte inconsistently across functions on a wide build.