昨日の続き。
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'まで変換した場合なので
かなーりマズイんでないかいな。