Not only is the Internet dead, it's starting to smell really bad.:2021年05月14日分

2021/05/14(Fri)

[オレオレN6] nvi-1.81のPOSIX character classが壊れてる件

かつてnvi-1.81がNにマージされた時に同梱されてるwide string regexがまともに動いてないのをいろいろ直した記憶があるんだが(作業開始がもう 13年前ってマジかよ)、今ようやっとPOSIX character class周りが動いてないのに今更ながら気づいた。

:%s/[[:digit:]]//g

とか叩いてなんで[[:alpha:]]な文字まで消えるんですかね…えーnvi-1.79の頃ってどうだったっけ…

そもそもワイは基本Cygwin上のvim使ってる時間の方が9割なのでな、それに置換もエディタでやらずにsed(1)とかperl(1)使う人なので使わん機能のバグなぞ気づかないのである。 これが時空の歪みが発生する最大の理由であって以下略

なお本家の方はとっとと1.81捨てて nvi2に移行してた記憶がうっすらとあるので、そっちだとちゃんと動いてる可能性はある。 でも今nvi2のregexの下かるく眺めた限りでは1.81とほとんど同じ(つーかワイがNにcommitした変更をnvi2の作者がそのまま持ってったはずなので)なのでダメかもしれない。 まぁわざわざ本家のN入れて試す気にもならないのでどうでもよろし。

そもそもあのwide string regex自体かなり無理矢理な実装なので、POSIX character classもC locale相当の値がハードコードだったりでガリガリとSAN値削られる実装なのだ。 なもんでどーせ将来的にlibcにmultibyte regex(3)入れなきゃならんのだしそれが終わればそいつで置き換えりゃいいと、楽観的展望で作業したからとりあえず常用の範囲で動きゃいいという雑な作業だったからな。

なんでそんな楽観的展望だったかというと根拠が無いわけではなく、GSoCでどっかの学生さんがmultibyte regex実装するぜっていってたからなんだけど、結局どうなったんだっけアレ。 なんか自分では実装は無理でした(笑)とか言いだして、その学生さん選んだmentorの質が問われるエンドだった記憶が…ああいうのばっかで後年NがGSoCから漏れたのまったく驚きは無かったゾ。

どうもN HEADのregex(3)のコードちら見した限りではF由来のマルチバイト対応コードがマージされてるっぽいが、あれ以前見た時にLC_COLLATEがらみでF独自のcollation API直接呼んでてそっ閉じしたんだがそのへんはどうしてんだろう?やっぱりどうでもいいや。

とりあえずwide string regexのベースとなってるHenry Spencer's regexじたいコードが古くてセキュリティ絡みのバグやら仰山あるので、まずはそこの同期してからPOSIX character classのバグ対策やね。

そもそもオレオレN6のlibcの方の実装も大概に古いのでFとOの修正履歴見ながらいろいろコードの更新をかけてるところ、それにしてもFのマルチバイト対応コードはあいかわらずクオリティが酷い…

[オレオレN6] 続・nvi-1.81

どうも根本的にPOSIX character classどころか[]が壊れてるっぽいなぁ。

:%s/[ABC]//g

で「A or B or C」だけでなく「あ」が消えたりとか無茶苦茶な動きをしよる…なんだこれ…

[オレオレN6] nvi2もダメ

とりあえずビルドにCMake要求されてキレそうになったのだけどCMakeのcmath問題を回避してビルド通してnvi2作ってみたがやっぱダメだね。 そもそもwide string regex周りのコードは俺がやっつけでwchar_t 32bit clean化したやつと同じだからそりゃ直ってないよな。

ということで13年間誰もバグに気づかない程度のユーザー数ってことだな、vimはニセモノでnvi最高とかいう声が聞こえてきたりもしたが誰も使ってないのがバレたゾ。きっとみんな本当はVSCodeとかAtom使ってるか未だnvi-m17nでUTF-8?なにそれなのだろう。

[オレオレN6] nvi-1.81/nvi2のcharacter class実装が壊れてる原因だいたい分かった

原因についてregex2.h読んで即座に理解したというかcharacter classを格納するcsetという構造体とそれを扱うマクロがまったくワイド文字対応されてないやん、ひでえなこりゃ。

struct {
	uch *ptr;		/* -> uch [csetsize] */
	uch mask;		/* bit within array */
	uch hash;		/* hash code */
	size_t smultis;
	char *multis;		/* -> char[smulti]  ab\0cd\0ef\0\0 */
} cset;
/* note that CHadd and CHsub are unsafe, and CHIN doesn't yield 0/1 */
#define	CHadd(cs, c)	((cs)->ptr[(uch)(c)] |= (cs)->mask, (cs)->hash += (c))
#define	CHsub(cs, c)	((cs)->ptr[(uch)(c)] &= ~(cs)->mask, (cs)->hash -= (c))
#define	CHIN(cs, c)	((cs)->ptr[(uch)(c)] & (cs)->mask)

はい、uchはunsigned charのhandy typeなので0~255より大きいワイド文字が入ってくると死ぬし、そもそも32bit wchar_tの幅のテーブルなんか持てない。 修正した当時たぶん薄々感づいててめんどくささから目を瞑ったと思われる、さぁガス管か練炭かロープか混ぜるな危険かどれにするか。

まぁ修正方法はリストなりで持てなのだがめんどくせえなぁ、Fのmultibyte regexは対応してるのでそれに合わせて実装するかだな。

typedef struct {
        wint_t          min;
        wint_t          max;
} crange;
typedef struct {
        unsigned char   bmp[NC_MAX / 8];
        wctype_t        *types;
        unsigned int    ntypes;
        wint_t          *wides;
        unsigned int    nwides;
        crange          *ranges;
        unsigned int    nranges;
        int             invert;
        int             icase;
} cset;

ワイド文字が0~255ならbmp、それ以上ならwidesあるいはrangesの配列という実装か。 真面目に実装するにはLC_COLLATEの実装ないと困るのだけどどうすんべ。

あと相変わらずFの連中はwchar_tについての知見が足りてないので

typedef uint32_t sop;	/* strip operator */
typedef uint32_t sopno;
#define	OPRMASK	0xf8000000U
#define	OPDMASK	0x07ffffffU
#define	OPSHIFT	(27U)
#define	OP(n)	((n)&OPRMASK)
#define	OPND(n)	((n)&OPDMASK)
#define	SOP(op, opnd)	((op)|(opnd))
/* operators			   meaning	operand			*/
/*						(back, fwd are offsets)	*/
#define	OEND	(1U<<OPSHIFT)	/* endmarker	-			*/
...

とwchar_tが32bit cleanではなく、26bitより上をフラグに流用してしまっておる。

これなぁwchar_tはあくまでopaque objectなので絶対にこういうコード書いちゃダメなんですよ。 毎回この手のクソコード書いてくるからFは信用ならねえのだよね。 つーかGB18030 localeで困るはずなんだけど、そいや無理矢理4バイトコード圧縮してたから26bitで収まるんかな。

これの正しい修正方法はワイがnvi-1.81のwide string regexでやったように

typedef uint32_t sop;	/* strip operator */
...
struct re_guts {
...
	sop *strip;		/* malloced area for strip */

typedef char sop;	/* strip operator */
...
struct re_guts {
...
	sop *strip;		/* malloced area for strip */
	wchar_t *stripdata;	/* malloced area for stripdata */

と分けるべきなのよね、たいしてメモリ使用量増えるわけでもないしなによりいちいちOP/OPNDマクロで文字とフラグを取り出す処理も無くなって可読性マシになるぞ。