bidi: do not pass non-ASCII codepoints to ispunct()

bidi_l2v() classified each character with ispunct((int)c), but c is
a decoded Unicode codepoint. ispunct() is only defined for values
that fit in an unsigned char (or EOF); a larger value is undefined
behaviour, and glibc then reads past the ctype table and reports
some codepoints as punctuation.

Hebrew final kaf (U+05DA) and gimel (U+05D2) land on such entries,
so a string ending in one had that letter trimmed off its RTL block
as if it were trailing punctuation and moved to the wrong end of
the line. Limit the punctuation test to ASCII so the result is
well-defined.

Change-Id: Ie6c3d8413f35ec3652e9228e3d5af05ef5bb811a
This commit is contained in:
elishay 2026-07-01 17:55:30 +00:00 committed by Solomon Peachy
parent c7d5603c63
commit 5c02cf3b3c

View file

@ -39,6 +39,12 @@
/* ischar() now lives in bidi.h as the shared is_rtl_char() macro */
#define _isblank(c) ((c==' ' || c=='\t') ? 1 : 0)
#define _isnewline(c) ((c=='\n' || c=='\r') ? 1 : 0)
/* ctype ispunct() is only defined for unsigned char values; feeding it a
* decoded Unicode codepoint (e.g. Hebrew final kaf U+05DA, gimel U+05D2) is
* undefined and some libcs then mis-report it as punctuation, which trims the
* final letter off its RTL block and throws it to the wrong end of the line.
* Restrict the punctuation test to ASCII. */
#define _ispunct(c) ((c) < 0x80 && ispunct((int)(c)))
#define XOR(a,b) ((a||b) && !(a&&b))
#ifndef BOOTLOADER
@ -203,7 +209,7 @@ ucschar_t *bidi_l2v(const unsigned char *str, int orientation)
do {
while((XOR(is_rtl_char(*(tmp+1)),block_type)
|| _isblank(*(tmp+1)) || ispunct((int)*(tmp+1))
|| _isblank(*(tmp+1)) || _ispunct(*(tmp+1))
|| *(tmp+1)=='\n')
&& block_end < length-1) {
tmp++;
@ -212,7 +218,7 @@ ucschar_t *bidi_l2v(const unsigned char *str, int orientation)
}
if (block_type != orientation) {
while ((_isblank(*tmp) || ispunct((int)*tmp))
while ((_isblank(*tmp) || _ispunct(*tmp))
&& *tmp!='/' && *tmp!='-' && block_end>block_start) {
tmp--;
block_end--;