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

2003/08/08(Fri)

(ry

@zW

昨日の続き。
b)だともうひとつ問題があるな。

#include <locale.h>
#include <wchar.h>
main()
{
	size_t len, ret;
	char buf[MB_LEN_MAX];
	wchar_t wc;
	mbstate_t from, to;

	setlocale(LC_CTYPE, "zh_CN.zW");

	memset(&buf, 0, sizeof(*buf));
 
	memset(&from, 0, sizeof(*from));
	len = wcrtomb(buf, L'z', &from);
  
	memset(&to, 0, sizeof(*to));
	ret = mbrtowc(&wc, buf, len, &to);
	^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

のようなケースを考えた場合、mbrtowcの呼び出し結果は

  a)
    buf = { 'z', 'W', ' ', 'z', '\n' } → ret = 5, wc = L'z' → 変換成功
  b)
    buf = { 'z' } → ret = (size_t)-2 → 変換失敗(つかrestart)

つー違いが出てきますね。
お行儀の良いプログラムならb)が問題になるようなケースは無いとは思うけども、
安全側に振るのであればa)でファイナル(ry

昨日の夜wcrtombだけb)でてきとーに実装したんだけど没かね。
ちなみにこんな感じ↓

typedef struct {
	int cell[UINT8_MAX+1];
#define	LEADBYTE	0x1
#define	TRAILBYTE	0x2
} _ZWEncodingInfo;

typedef struct {
	int chlen;
	char ch[5]; /* L'W'= "W z\nW" or ascii->gb2312 = "\nzWxx" */

	int charset;
#define	INIT		0
#define	AMBIGIOUS	1
#define	ASCII		2
#define	GB2312		3

} _ZWState;

static int
_citrus_ZW_encoding_module_init(_ZWEncodingInfo * __restrict ei,
				const void *__restrict var, size_t lenvar)
{
	int i;

	memset(ei, 0, sizeof(*ei));
	for (i = 0x21; i <= 0x7e; i++)
		ei->cell[i] |= LEADBYTE;
	for (i = 0x21; i <= 0x7e; i++)
		ei->cell[i] |= TRAILBYTE;

	return (0);
}

static int
_citrus_ZW_wcrtomb_priv(_ZWEncodingInfo * __restrict ei, char *__restrict s,
			size_t n, wchar_t wc, _ZWState * __restrict psenc,
			size_t * __restrict nresult)
{
	int charset;
	u_int8_t c;

	/* check chlen */
	if (psenc->chlen != 0)
		goto invalid;

	/* check charset */
	switch (charset = psenc->charset) {
	case NONE: case AMBIGIOUS: case ASCII: case GB2312:
		break;
	default:
		goto invalid;	
	}

	if ((u_int32_t)wc < 0x100U) {
		if (wc == L'\0' || wc == L'\n') {
			switch (charset) {
			case GB2312:
				if (n-- < 1)
					goto e2big;
				psenc->ch[psenc->chlen++] = (wc == L'\0') ? '\n' : '#';
			/*FALLTHROUGH*/
			case ASCII:
			/*FALLTHROUGH*/
			case AMBIGIOUS:
				charset = NONE;
			}
		} else {
			switch (charset) {
			case NONE:
				charset = (wc == L'z') ? AMBIGIOUS : ASCII;
				break;
			case AMBIGIOUS:
				if (wc == L'W') {
					if (n < 4)
						goto e2big;
					n -= 4;
					memcpy(&psenc->ch[psenc->chlen], "W z\n", 4);
					psenc->chlen += 4;
				}
			/*FALLTHROUGH*/
			case GB2312:
				charset = ASCII;
				break;
			}
		}
		if (n-- < 1)
			goto ilseq;
		psenc->ch[psenc->chlen++] = (char)(u_int8_t)(u_int32_t)wc;

	} else {
		switch (charset) {
		case ASCII:
		/*FALLTHROUGH*/
		case AMBIGIOUS:
			if (n-- < 1)
				goto e2big;
			psenc->ch[psenc->chlen++] = '\n';
		/*FALLTHROUGH*/
		case NONE:
			if (n < 2)
				goto e2big;
			n -= 2;
			memcpy(&psenc->ch[psenc->chlen], "zW", 2);
			psenc->chlen += 2;
			charset = GB2312;
		/*FALLTHROUGH*/
		case GB2312:
			if (n < 2)
				goto e2big;
			n -= 2;
			c = (u_int8_t)((u_int32_t)wc >> 8);
			if (!(ei->cell[c] & LEADBYTE)) 
				goto ilseq;
			psenc->ch[psenc->chlen++] = (char)c;
			c = (u_int8_t)(u_int32_t)wc;
			if (!(ei->cell[c] & TRAILBYTE))
				goto ilseq;
			psenc->ch[psenc->chlen++] = (char)c;
			break;
		default:
			goto invalid;
		}
	}

	memcpy(s, psenc->ch, psenc->chlen);
	*nresult = psenc->chlen;
	psenc->chlen = 0;
	psenc->charset = charset;

	return (0);

invalid:
	return (EINVAL);

e2big:
	return (E2BIG);

ilseq:
	*nresult = (size_t)-1;
	return (EILSEQ);
}

そしてmbrtowc。

wchar_t wc;
mbstate_t ps;
char c;

memset(&ps, 0, sizeof(ps));
c = 'z';
len = mbrtowc(&wc, &c, 1, &ps);
c = 'a';
len = mbrtowc(&wc, &c, 1, &ps);

1回目 len = -2でrestart、psの中身は↓となる。

    (_ZWState)ps = { 1, { 'z', } AMBIGIOUS }

2回目 len = 1、wc = L'z'で変換成功するんだけど、
psの中身は↓になってしまっている。

    (_ZWState)ps = { 1, { 'a', } ASCII }

mbstate_tにvalidなsingle-byteである'a'が残ってしまう。
この時点ですでにマズーなんだけど無視し、さらに続けて

c = 'b';
len = mbrtowc(&wc, &c, 1, &ps);

を実行すると、
3回目 len = 0、wc = L'a'という結果が戻ってくることになる。

SUS/C99的にはlen = 0を返すケースは通常はL'\0'まで変換した場合なので
かなーりマズイんでないかいな。