蝉は、やがて死ぬる午後に気づいた。ああ、私たち、もっと仕合せになってよかったのだ。:2010年03月分

2010/03/01(Mon)

[NetBSD] libedit I18N への道(その5)

昨日の補足だけ。

どうにも春の頭痛胃痛ダブル祭りが絶賛開催中なもんでして…ちうことで
バッファリングとかするとバグらせる悪寒、俺に必要なのはバッファリンに違いない。
なんで余計なことは libc にお任せの高水準API使って書き直しちゃるぞーということで。

今回のケースでは元々動作が怪しいので尊重すべきは
ワイド文字対応コードが入る前のソースであるからして、すべてバックアウトした状態
つまり rev1.30をベースに作業することにしますか。

その1、vi(1)に渡す一時ファイルを作成する部分のオリジナル。

1013 	fd = mkstemp(tempfile);
1014 	if (fd < 0)
1015 		return CC_ERROR;
1016 	cp = el->el_line.buffer;
1017 	write(fd, cp, (size_t)(el->el_line.lastchar - cp));
1018 	write(fd, "\n", 1);
1019 	pid = fork();

説明の必要ありませんね?

書き直したコード。

1016 	fd = mkstemp(tempfile);
1017 	if (fd < 0)
1018 		return CC_ERROR;
1019 	fp = fdopen(fd, "w+");
1020 	if (fp == NULL) {
1021 		close(fd);
1022 		return CC_ERROR;
1023 	}
1024 	for (cp = el->el_line.buffer; cp < el->el_line.lastchar; ++cp) {
1025 		fputwc(*cp, fp);
1026 		if (ferror(fp))
1027 			goto fatal;
1028 	}
1029 	fputwc(L'\n', fp);
1030 	if (ferror(fp))
1031 		goto fatal;
1032 	fflush(fp);
1033 	pid = fork();
1034 	switch (pid) {
1035 	case -1:
1036 		goto fatal;
1037 	case 0:
1038 		fclose(fp);
1039 		execlp("vi", "vi", tempfile, (char *)NULL);
1040 		exit(0);
1041 		/*NOTREACHED*/

という感じ。

その2、vi(1)が編集した一時ファイルを読み込む部分のオリジナル。

1031 		while (waitpid(pid, &status, 0) != pid)
1032 			continue;
1033 		lseek(fd, (off_t)0, SEEK_SET);
1034 		st = read(fd, cp, (size_t)(el->el_line.limit - cp));
1035 		if (st > 0 && cp[st - 1] == '\n')
1036 			st--;
1037 		el->el_line.cursor = cp;
1038 		el->el_line.lastchar = cp + st;
1039 		break;

説明の(ry

書き直し(ry

1043 		while (waitpid(pid, &status, 0) != pid)
1044 			continue;
1045 		unlink(tempfile);
1046 		rewind(fp);
1047 		for (cp = el->el_line.buffer; cp < el->el_line.limit; ++cp) {
1048 			ch = fgetwc(fp);
1049 			if (ferror(fp))
1050 				goto fatal;
1051 			if (feof(fp))
1052 				break;
1053 			*cp = ch;
1054 		}
1055 		if (cp != el->el_line.buffer && *(cp - 1) == L'\n')
1056 			--cp;
1057 		el->el_line.cursor = el->el_line.buffer;
1058 		el->el_line.lastchar = cp;
1059 		break;

あとは-DWIDECHARなしでbuildする場合のためにchartype.hに

#ifdef WIDECHAR

#define ct_fgetwc	fgetwc
#define ct_fputwc	fputwc

#else

#define ct_fgetwc	fgetc
#define ct_fputwc	fputc

#endif

としときましょう。

ただしオリジナルのコードと比較すると -UWIDECHAR の場合には read/write 一発でなく
fgetc/fputc の複数回の呼び出しになるので system call の呼び出し回数は libc が
適切にバッファリングでセーブするとはいえ、ライブラリ関数の呼び出し回数が今度は増えちゃいますやね。
もし性能にシビアな要求がある場合は、vi.c 側で ifndef WIDECHAR の場合は fread/fwrite を
使うことも検討する必要があります、まぁ今回はせいぜい一行(1024文字)までなんでやらんけど。

とりあえずこれでLANG=ja_JP.eucJPで起動したsh(1)でset -o viし、ESC + v押下で
historyをvi(1)上から編集できるのですがvi_histedit()から先でバグってるようで
マルチバイトの場合、実行されるhistoryが不正に切り詰められちゃってる模様。
多分ワイド文字数とバイト数がどっかでごっちゃなんだろうなー。

[C locale の場合]
vmware$ LANG=C sh
vmware$ set -o vi
vmware$ <ESC> v
\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa
~
~
~
:w!
vmware$
あいうえお: not found
vmware$

[ja_JP.eucJP locale の場合]
vmware$ LANG=ja_JP.eucJP sh
vmware$ set -o vi
vmware$ <ESC> v
あいうえお
~
~
~
:w!
vmware$
あいう: not found
vmware$

現時点での差分は こちら

次回はこの切り詰め問題を追っかける予定ですがいつもの通り未定 :D

2010/03/02(Tue)

[NetBSD] libedit I18N への道(その5.1)

昨日の補足再び、sh(1)でhistoryが切り詰められてしまう問題ですが、既に第3回でヒントが出てましたな。
el_gets(3)は IGNORE_EXTCHARS を使い el_wgets(3) の挙動を変更することで実装されてるという件。

eln.c

72 public const char *
73 el_gets(EditLine *el, int *nread)
74 {
75 	const wchar_t *tmp;
76
77 	el->el_flags |= IGNORE_EXTCHARS;
78 	tmp = el_wgets(el, nread);
79 	el->el_flags &= ~IGNORE_EXTCHARS;
80 	return ct_encode_string(tmp, &el->el_lgcyconv);
81 }

ここで第2引数で返してる文字列の幅がワイド文字数のままのオカン。

read.c

504 		*nread = (int)(el->el_line.cursor - el->el_line.buffer);
505 		goto done;

ズコー、IGNORE_EXTCHARS をセットした意味ないじゃんよこれwww
まー el_line ではワイド文字で持ってるんで、どうしても el_wgets(3) の中では計算できないのは確か。

手堅く修正するのであれば ct_encode_string() のIFを弄って、ワイド文字→マルチバイトに変換された
バイト数を返すように修正する鹿、なのですがもう正直 eln.c を一から書き直したい衝動がゴゴゴゴゴ。

まぁ本格対応の前に暫定fixを入れてみる。

Index: eln.c
===================================================================
RCS file: /cvsroot/src/lib/libedit/eln.c,v
retrieving revision 1.6
diff -u -r1.6 eln.c
--- eln.c       20 Jan 2010 01:15:52 -0000      1.6
+++ eln.c       2 Mar 2010 14:54:17 -0000
@@ -73,11 +73,15 @@
 el_gets(EditLine *el, int *nread)
 {
 	const wchar_t *tmp;
+	const char *s;

 	el->el_flags |= IGNORE_EXTCHARS;
 	tmp = el_wgets(el, nread);
 	el->el_flags &= ~IGNORE_EXTCHARS;
-	return ct_encode_string(tmp, &el->el_lgcyconv);
+	s = ct_encode_string(tmp, &el->el_lgcyconv);
+	if (nread != NULL)
+		*nread = strlen(s);
+	return s;
 }

これで実行してみると

vmware$ LANG=ja_JP.eucJP sh
vmware$ set -o vi
vmware$ <ESC> v
あいうえお
~
~
~
:w!
vmware$
あいうえお: not found
vmware$

うまくいきました\(^o^)/

2010/03/05(Fri)

[NetBSD] libedit I18N への道(その5.2)

@ふりだしに戻る

なにやら不吉なサブタイトルですが。

さて 前回は el_gets() の返す文字幅がバイトでなくワイド文字になってる件の対策で
ct_encode_string() のインタフェースを変更し、ワイド文字→マルチバイトの変換で
何バイト変換されたかを返すようにすればいいんじゃね?という結論になりましたが
これには実は巨大な落とし穴があります。

なぜなら el_gets() はそもそも内部で mbrtowc(3) のたぐいは使ってはならないのです。
これは 大惨禍い第3回で el_getc() の実装について説明した理由とそのまんま同じです。
現状のコードでは、LANG=ja_JP.eucJP で { 0xA1, 0x41, 0x0 } みたいなマルチバイトを喰わせると
これをワイド文字に変換しようとして変換エラーで EILSEQ になってしまうのですが
el_gets() はあくまでバイト志向の API なのでこれは許されないのですよ、うーアボガドバナナ。


いう
 こと
   は

この問題はかなり致命的です、現在 editline 構造体において一行分のデータは
el_line フィールド、すなわち el_line_t 構造体で管理しているのですが

74 typedef struct el_line_t {
75 	Char	   *buffer;        /* Input line                   */
76 	Char	   *cursor;        /* Cursor position              */
77 	Char	   *lastchar;      /* Last character               */
78 	const Char *limit;         /* Max position                 */
79 } el_line_t;
...
116 struct editline {
...
128 	el_line_t         el_line;      /* The current line information */
...

ちゅうように、すべて Char(=wchar_t) すなわちワイド文字として行情報を保持してやがるのです。
ですのでマルチバイト→ワイド文字変換をしない限り、行情報を保持できないという。困った。

こりゃ完全に設計ミスですな、ちうことは この変更は全部 backout して元に戻さんとならん悪寒。

/(^o^)\ナンテコッタイ

histedit.h では LineInfo と LineInfoW と別なのに…どうしてこうなった…

2010/03/06(Sat)

[NetBSD] libedit I18N への道 (その5.3)

@この patch は出来そこないだ。食べられないよ by 山岡士郎

さてさて、いろいろ ガラガラ崩れはじめとりますが、もういっちょ救いようのない
問題を発見してしまいましたが、俺はもう限界かもしれない。

editline(3)には以下の機能があります。

     el_set()
           Set editline parameters.  op determines which parameter to set, and
           each operation has its own parameter list.

           The following values for op are supported, along with the required
           argument list:

           EL_GETCFN, int (*f)(EditLine *, char *c)
                 Define the character reading function as f, which is to
                 return the number of characters read and store them in c.
                 This function is called internally by el_gets() and
                 el_getc().  The builtin function can be set or restored with
                 the special function name ``EL_BUILTIN_GETCFN''.

要するに tty(4) から文字(列)を読み込むための関数を override できる機能なのです。
EL_BUILTIN_GETCFN というのは正に 第3回でいじった read_char() そのものです。

さっそく el.c のコードをみてみませう。

281 	case EL_GETCFN:
282 	{
283 		el_rfunc_t rc = va_arg(ap, el_rfunc_t);
284 		rv = el_read_setfn(el, rc);
285 		el->el_flags &= ~NARROW_READ;
286 		break;
287 	}

el_set(3)の引数は可変引数リストですので、283行目で va_arg(3) を使って取得します。
ここでは override するint (*f)(Editline *, char *)型の関数ポインタは
read.h で typedef される el_rfunc_t となっとるのですが、で・す・が。

@@ -35,7 +35,7 @@
 #ifndef        _h_el_read
 #define        _h_el_read

-typedef int (*el_rfunc_t)(EditLine *, char *);
+typedef int (*el_rfunc_t)(EditLine *, Char *);

 typedef struct el_read_t {
        el_rfunc_t      read_char;      /* Function to read a character */

ぎゃー勝手にインタフェースを char -> Char(=wchar_t) に変更してやがんの。
これはひどい、ひどすぎる。

お分かりだと思いますが、これバイナリの後方互換をパーペキに失ってます。
後方互換を失うって何?という人は 以前の記事読んでください。

ですので古いバイナリが int (*)(EditLine *, char *) の関数ポインタを新しい libedit に渡してしまい
int (*)(EditLine *, wchar_t*)として呼ばれたら、まずまともに動かないことでしょう。

ま唯一の救いは、新しい libeditは char よりも大きな wchar_t のサイズを持つ
記憶領域へのポインタを古いバイナリ側が用意した override 関数に渡すので
まずbuffer overflow は発生しないだろうということくらいっすか。
# つったって、(この機能を使ってないとはいえ)sh(1)とかちょー重要なアプリがリンクするライブラリでこれはねーよ…

ということで、次回からは1月の変更をすべて Reset / Mutemathして(笑)
libedit を一から i18n 化する連載ということでwwwちょwww

2010/03/07(Sun)

[NetBSD] libedit I18N への道 (その5.3.1)

@

ちうことで、さすがにバイナリ互換壊れたままは不味いもう一杯なので メールかいたのですが
このpatchをcommitした christos氏曰く 大丈夫じゃね?と。

というのも、前回書いた話

ま唯一の救いは、新しい libeditは char よりも大きな wchar_t のサイズを持つ
記憶領域へのポインタを古いバイナリ側が用意した override 関数に渡すので 

正にこの事実を逆に利用して、el_wgetc(3) の中で

420         num_read = (*el->el_read.read_char)(el, cp);
421 #ifdef WIDECHAR
422         if (el->el_flags & NARROW_READ)
423                 *cp = *(char *)(void *)cp;
424 #endif

として、wchar_t を char に整形してたという、汚いなさすが kludge きたない

この NARROW_READ flag は el_set(3) の中でセットしてまんな。

        /* XXX: do we need to change el_rfunc_t? */
        case EL_GETCFN:         /* el_rfunc_t */
                ret = el_wset(el, op, va_arg(ap, el_rfunc_t));
                el->el_flags |= NARROW_READ;
                break;


そもそも el_set(3) があるのに el_wset(3) とか必要ねーだろ説。
それにdo we needとか疑問形にせんでも、EL_WGETCFN を追加して
ワイド文字版の el_rfunc_t を別定義するが普通だと思います、まぁもう手遅れですが。

めんどくさくなったのでもうバイナリ互換OKならなんでもいいや。

しかしそもそも現在 EL_GETCFN のデフォルト値である EL_BUILTIN_GETCFN(=read_char)
を置き換えられちゃうと、マルチバイト→ワイド文字の変換をやっとるのはここなので
el_set(EL_GETCFN) されると el_wgetc(3) でまともにMB_CUR_MAX > 1な文字が扱えなくなりますな。
そこは 理解していただいてるようでなにより。

2010/03/12(Fri)

NANですか~

NANの作り方、 これとか

isnan(0.0/0.0);
isinf(1.0/0.0);
isinf(-1.0/0.0);

ちうのもあります、こないだ NetBSD の libm 直したときに 定数 NAN と ifdef __vax__ 使ったら
drochner@ がこれ使えば MD でなく MI で書けるぜぃと修正しとったです、はい。
ちなみに ((float)((1e308 * 10)*0.)) は NetBSD ではダメっす。

まぁこれ fpgetmask(3) で FP_X_DZ たってる場合ははずす必要あるのと(FreeBSD ってまだそうだっけ?)
VisualC++ の場合は C2124なので、分母の0.0を定数で書けませんのでいちど変数に入れる必要が。

2010/03/13(Sat)

[NetBSD] 6.0 への最低限のTODO

締め切りが近づかないと何も作業しない悪い癖(ぉ
まぁ普段*BSDとは縁もゆかりもない、人月SE消耗品やってりゃそうもなる罠。

2010/03/14(Sun)

[NetBSD] TODO追加

もういっちょ忘れてた、wchar_t を int から unsigned int(or uint32_t) に変更して
32bit through にするか否かのジャッジメントですの。

ついさっきまで単純文字定数は型 int を持つ(6.4.4.4)という仕様に気を取られてて

	wchar_t *ws = ...

	if (*ws == '\0')
		...

みたいな wchar_t と単純文字定数の比較なんてよくあるコードに gcc -Wsign-compare 的な
compiler の警告が引っかかったら困るよなーとか思って「むりむりカタツムリ」だと
思ってたけど、これ警告出ないよね :-P まずちゃんと試してから発言せいや>>俺

こっち↓のケースはたうぜん警告出るけど、これはcastしない香具師が悪いか。

	wchar_t *ws = ...
	int y = '\0';

	if (*ws == y)
		...

まそもそも wchar_t が negative value 持ったって問題ないのだけどね
char だって signed な環境なら negative value 持つのと同じだよねー。

ただ strcmp(3) の戻り値って unsigned char 同士で比較した結果を返すのだけど
その wchar_t 版である wcscmp(3) の戻りは unsigned wchar_t なんてものは無いので
どうすんの?ってだけ(NetBSD では __nbrune_t を使うことで回避している)。

ま、仕様上では strcmp(3)だと

both interpreted as type unsigned char

の一文があるけど、 wcscmp(3)の方は wchar_t の unsigned 表現にして比較しろなんて一文はどこにもないので
何の問題もないという話もある、wcscmp(3)GB18030 localeで4byteコードが1byteコードよりも
小なりになったっていいじゃないというのがCSI道なのかも。
辞書順はたうぜん wcscoll(3) 使えってことですな。

よくある間違い「それwint_tで」というのは大きなマテガイで、あれは wchar_t の
sizeof が 1(=char) とか 2(=short) な実装を念頭において、wchar_tを引数とした
場合に、暗黙の整数拡張が行われた時に発生する問題を考慮して導入されたもんであって
決して wchar_t に対応する符合無し整数型を収める為に十分な大きさを持つ整数型
ではないのよな、32bit なら通常同じ表現だし。

まぁどっちもUCS4 hardwired wchar_tの場合は最大21bitだから問題にならんからねぇ。

2010/03/21(Sun)

[NetBSD] TODOs 消化

@

お外は黄砂と花粉で地獄絵図なのでひきこもりしてこないだの TODOs をちまちま消化中。

それと6.0に間に合うかわからんけど、date(1) の表示の改善と strftime %+ の実装、そして
lib/41039でリクエストきてる ISO/IEC TR14652 extension を実装中。
あ、あと ERA と ALT_DIGITS は nl_langinfo(3) のみサポートで。
ちょっと strftime(3) までは見てる時間なさげ。

やぱし困ったことに去年もめた plain-text を locale-db にする localeio.c の実装との互換が
最低なことになってて泣けてきた、ERAなんかのキーワードはlocaledef(1)では省略可能なのだけども
いまの src/share/locale/time/* の書式はただ上から下に行でずらずら並べるだけなので
省略のしようがないという、ほんにもうったら。

あとPOSIX 2008 で導入された mbsnrtowcs(3)/wcsnrtombs(3) は実装したので最難関のテストケース書き(ぇ
こっちは去年書いたThread Aware Locale Modelもmergeして、6の枝切り後にでも
tnozaki-locale-posix2008 枝申請ですかね。
去年 joerg氏に objection もらっとるのだけど(要約するともっとカッコイイAPI作ろうぜ)
さすがに POSIX の御旗がありゃ今回は押し切れるだろうという甘い予感。

2010/03/22(Mon)

TR14652 extension for LC_TIME

ISO/IEC TR14652[pdf]のダメなところは、せっかく新しいフィールド追加したのに、それを使うためのAPIが
nl_langinfo(3)に指定する nl_item 定数を含めてまったくどこにも定義されてないことだよな。

たぶん唯一の TR14652 実装である glibc2 では nl_item 定数として

[nl_item定数]			[locale(1) -k]
_NL_TIME_WEEK_NDAYS		week-ndays
_NL_TIME_WEEK_1STDAY		week-1stday
_NL_TIME_WEEK_1STWEEK		week-1stweek
_NL_TIME_FIRST_WEEKDAY		first_weekday
_NL_TIME_FIRST_WORKDAY		first_workday
_NL_TIME_CAL_DIRECTION		cal_direction
_NL_TIME_TIMEZONE		timezone

が定義されてるんだけど、これ将来的にどーなるかわからんので、NetBSD/Citrusの
_locale_t(=_locale_impl_t) に用意したキャッシュ(=items)には含めたくないなぁ、と。

struct _locale_cache_t {
	const unsigned char *ctype_tab;
	const short *tolower_tab;
	const short *toupper_tab;
	size_t mb_cur_max;
	struct lconv ldata;
	const char *items[ALT_DIGITS + 1];
};

struct _locale_impl_t {
        struct _locale_cache_t cache;
...

ということで他の normal な nl_item定数と区別するために、MSB立てるとかしときたいのだけど

typedef long nl_item;

と signed の MD な型だったりするので微妙に書きづらい、LONG_MINとか使うのはevilだしなぁ。どしよ。

そもそもglibc2レベルでnl_itemを増やすと、全部のitemに対してcacheを用意するのは無理があるのよな。

	wchar_t *ws;
	ws = (wchar_t *)(char *)nl_langinfo(_NL_WABDAY_1);

みたいにchar*にキャストしてワイド文字返したりもする、かなりフリーダムな実装なんで非常に頭が痛い。
# ただしこの方がwprintf(3)なんかの実装に性能的に有利ではある、なんというパラノイア。

ま、どっちにしろ今後 struct lconv のフィールドや nl_item の数が増えることを想定して
struct _locale_cache_tの実装は直しといた方がいいよな。

--- setlocale_local.h   2 Dec 2009 08:53:03 -0000       1.3
+++ setlocale_local.h   21 Mar 2010 19:24:05 -0000
@@ -40,12 +40,12 @@
        const short *tolower_tab;
        const short *toupper_tab;
        size_t mb_cur_max;
-       struct lconv ldata;
-       const char *items[ALT_DIGITS + 1];
+       struct lconv *ldata;
+       const char **items;
 };

 struct _locale_impl_t {

↑の方が後々よさげ、去年実装したとき手抜き過ぎた。

といっても locale_t はコンストラクタありなので後方互換は問題にならないし
別に今の実装のままで増やしてしまってもいいのだけど。でもしかしこの部分って

__inline struct lconv*
localeconv_l(locale_t l)
{
	return ((struct _locale_impl_t)l)->cache.ldata);
}
__inline char *
nl_langinfo_l(nl_item item, locale_t l)
{
        const char *s;
        s = NULL;
        if (item >= D_T_FMT && item <= ALT_DIGITS) {
                
                s = ((struct _locale_impl_t)l)->cache.items[(size_t)item];
        }
        if (s == NULL)
                s = "";
        return __UNCONST(s);
}

みたいにinlineとして提供し性能を稼ぐ必要あるかもなーという想定で実装してたかんね(俺もパラノイア)。
そうすっと struct lconv のフィールドや nl_item の数が増えると後方互換が壊れるので
回避にあんまり美しくないコードを書く羽目に。

うし、この変更だけは少なくとも 6.0 にはつっこもう。

[C1X] *cpy_s in TR24731-1

_tcscpy_sネタ。
ISO/IEC TR24731-1[pdf]では以下の場合、set_constraint_handler_s(3)でセットした例外ハンドラを呼ぶことになってますな。
(手元のNetBSD用に 以前書いた実装をみて書いてるので最新は知らんがな)

TR24731-1では例外ハンドラはデフォではNULLなのでそのまま処理続行のはず。
まぁVC++だとデフォでabort_handler_s(3)がセットされてるのじゃないでしょうか。

まぁそれはおいといて、元の質問見たけど

 numberOfElements

    コピー先の文字列バッファのサイズ。

をバイト数と勘違いする人がいるのだなぁと、あくまでコピー先の文字型の配列長でんがな。
これを誤植とかもうね(以下略

int bytes = ( ( _tcslen(c) + 1 )*sizeof(TCHAR) );
if ( fn = (TCHAR*)malloc( bytes ) ) _tcscpy_s( fn, bytes, c );

じゃなくて

int len = ( ( _tcslen(c) + 1 ) );
if ( fn = (TCHAR*)cmalloc( len, sizeof(TCHAR) ) ) _tcscpy_s( fn, len, c );

だぁね、つか長さ調べてその分アロケートするなら_tcscpy_s使う意味ないし、この関数は

TCHAR buf[BUFSIZ];
_tcscpy_s(buf, BUFSIZ, c); 

のように、固定長バッファをあふれさせないように使う関数だかんね。
リンク先の回答にもあるとおり動的割り当ておkなら _tcsdup() 使えと。

2010/03/28(Sun)

[NetBSD] TODOs 消化中

とりあえず _locale_cache_t を将来的にexposedにした場合でなおかつ
nl_items が増えた場合に発生するであろうバイナリ互換性問題の対策 done.
なので ftp に放置プレイしてる multi-locale 実装も修正する必要があるのだけど
まぁこれは 6.0向けの作業が終わったら本気だすので後回し。

直後に去年おいらがしでかした s/Augst/August/ の間違いをchristos@が直してくれたのですが

fix tpo.

という commit log の再帰感がたまらなく好きです。

mbstate_t/wctype_t/wctrans_t の MI 化も done.

そんで MB_LEN_MAX の MI 化も片付けようと思ったのだけどこれが困った。

ただの人間には興味ありません。この中にNetBSD/hp700使いで、ja_JP.ISO-2022- JP、
ja_JP.ISO-2022-JP2、ja_JP.ct localeを常用してる人がいたら、あたしのところに来なさい。
以上。

この条件に当てはまる人はもれなく buffer overrun の可能性があります。
こんなん世に何人いるかというと、非実在青少年認定してもいいレベルだよなー

実在しました

かつて NetBSD では Citrus の merge に先立って、multibyte locale が使用できるよう
それまで MB_LEN_MAX=1 だったものを32に増やしたのですが( このへん参照)、386BSD由来のコードでMB_LEN_MAXは
MDとして宣言されてたものまでは修正していなかったので、2002年つまり Citrus merge 後に
portingされた CPUARCH=hppa な NetBSD/hp700 ではどうやら FreeBSD の limits.h の実装を参考にしたのか
MB_LEN_MAX=6 として commitされてしまいよってます(FreeBSD に hppa なんてないのにな、なんでだー)
ところが src/libc/locale/setlocale32.c では __mb_len_max_runtime の値を32でハードコードしてるので
buffer overrunする可能性が。

39 char *
40 __setlocale_mb_len_max_32(category, locale)
41 	int category;
42 	const char *locale;
43 {
44
45 	/* locale may be NULL */
46
47 	__mb_len_max_runtime = 32;
48 	return __setlocale(category, locale);
49 }

まぁ ISO-2022-JP なら冗長な escape sequence が発生しなければ最長6バイトなので
これまた悪意のある冗長なエスケープシーケンスを送り付けない限り問題にもならないので
多分今日まで一度もこのバグは踏まれたことはないであろうレベル。

ということでとりあえずは s/32/MB_LEN_MAX/ してbuffer overrunを防いで
hppa の MB_LEN_MAX 6 -> 32 化と MB_LEN_MAX 自体の MI化は libc major bump
のタイミングまでお預けということにしますか。