[pgsql-jp: 36515] Re: JISX0212領域でのstrpos

Tatsuo Ishii ishii @ sraoss.co.jp
2005年 12月 9日 (金) 23:27:34 JST


石井です。

> こんにちは。石田@苫小牧市です。
> 
> EUC_JP環境でのJIS X 0212領域の扱いについて気になる動作が
> あったので識者のご意見をお聞きしたいです。

識者ではありませんが、バグを入れてしまった犯人です。

> PostgreSQL は 8.0.4、データベースエンコーディング、
> クライアントエンコーディングはともに EUC_JP です。
> また、検証に私が作ったhextostrという
> ユーザ定義関数を使っています(strtohexの逆の動作をするものです)。
> http://mono-space.net/doc/func_hextostr/

これ、便利ですね。早速いただきました。

> 以下、実行結果にJIS X 0212領域の文字が出てきますが、
> メールにはふさわしく無いので、別の文字に置き換えています。
> 
> 
> 現象
> strpos(text, text)で、別の文字にマッチしてしまう。
> ----
> ishida=# SELECT hextostr('8faaa18faae1'), hextostr('8faae1');
>  hextostr | hextostr
> ----------+----------
> AT(*1)  |T(*2)
> 
> ishida=# SELECT strpos(hextostr('8faaa18faae3'), hextostr('8faaa1'));
>  strpos
> --------
>       1       -- 問題無し
> (1 row)
> 
> ishida=# SELECT strpos(hextostr('8faaa18faae3'), hextostr('8faae1'));
                                              ~1ですよね?
>  strpos
> --------
>       1       -- 2になるはずでは?
> (1 row)
> 
> ----
> 
> 処理内容を正しく理解している自信は無いのですが、
> pg_euc2wchar_with_len (src/backend/utils/mb/wchar.c) で
> 
>         else if (*from == SS3 && len >= 3)
>         {
>             from++;
>             *to = *from++ << 8;
>             *to |= 0x3f & *from++;
>             len -= 3;
>         }
> 
> と 0x3f と AND をとっているときに桁落ちしてしまっている
> ために、pg_wchar同士の比較が正しくない結果を返してしまう
> のでははないかと推測するのですが、いかがでしょうか。

はい、その通りで、ここで余計なことをやっているがためのバグです。

なんでこうなってしまったかの背景をお話しします。随分前の話なので間違っ
ている部分があるかもしれませんが...

7.4より前のマルチバイト対応実装では、Henry Spencer氏の正規表現ルーチン
を改造して使っていました。そこでは以下のような処理をしていました。

1) マルチバイト文字を wide character(4バイト)に変換する

2) その時、文字自体は3バイト以内とし、頭1バイトで文字集合を表すことに
   する(文字集合識別)

3) 文字集合識別 + 文字自体のビットマップ(3バイト = 16777216ビット)表現
   を操作して正規表現処理をする

ここで問題なのは、文字自体を3バイトに納める、という制限です。EUCエンコー
ディングを前提にすると、EUC-TWのように4バイトになってしまうケースもあ
ります。そこで、EUCのマルチバイト -> wide character の変換の際に、
SS(single shift)2やSS3を外して、1バイトを稼ぐことにしました。しかしそ
のままでは SS3を外した JIS X 0212 と、JIS X 0208 の区別がつかなくなっ
てしまうので、JIS X 0212 の方は2バイト目の8ビット目を落とし、JIS X
0208 の方はそのままにしました。JIS X 0208 は1バイト目も2バイト目も必ず
8ビット目が立っているため、JIS X 0212と混同する心配が無くなります。

しかし、(ここで記憶が曖昧なのですが:-)、なぜか8ビット目を落とす処理を
0x7fとのANDではなく、0x3fとのANDにしてしまいました:-< すると、

a1 & 0x3f = 21
e1 & 0x3f = 21

となり、a1 = e1 と見なされて、石田さんの指摘する strpos の誤動作が起き
るわけです。

7.4以降では、正規表現ルーチンの実装が変更され、「3バイト以内に納める」
という考慮はいらなくなったので、上記のような小細工はする必要がなくなり
ました。

というわけで、7.4以降では添付のようなパッチをwchar.cに当てれば直ると思
います(currentで作ったパッチですが、8.0系でもあたると思います)。

以下、テスト結果です。

test=# SELECT strpos(hextostr('8faaa18faae3'), hextostr('8faaa1'));
 strpos 
--------
      1
(1 row)

test=# SELECT strpos(hextostr('8faaa18faae1'), hextostr('8faae1'));
 strpos 
--------
      2
(1 row)

test=# SELECT hextostr('8faaa18faae1') ~ hextostr('8faae1');
 ?column? 
----------
 t
(1 row)

test=# SELECT hextostr('8faaa18faae1') LIKE '%' || hextostr('8faae1');
 ?column? 
----------
 t
(1 row)

SELECT hextostr('8faae18faaa1') LIKE hextostr('8faae1') || '%';
 ?column? 
----------
 t
(1 row)

*** wchar.c.orig	2005-10-29 09:31:52.000000000 +0900
--- wchar.c	2005-12-09 20:31:59.000000000 +0900
***************
*** 66,91 ****
  
  	while (len > 0 && *from)
  	{
! 		if (*from == SS2 && len >= 2)
  		{
  			from++;
! 			*to = 0xff & *from++;
  			len -= 2;
  		}
! 		else if (*from == SS3 && len >= 3)
  		{
  			from++;
! 			*to = *from++ << 8;
! 			*to |= 0x3f & *from++;
  			len -= 3;
  		}
! 		else if ((*from & 0x80) && len >= 2)
  		{
  			*to = *from++ << 8;
  			*to |= *from++;
  			len -= 2;
  		}
! 		else
  		{
  			*to = *from++;
  			len--;
--- 66,91 ----
  
  	while (len > 0 && *from)
  	{
! 		if (*from == SS2 && len >= 2)	/* JIS X 0201 (so called "1 byte KANA") */
  		{
  			from++;
! 			*to = (SS2 << 8) | *from++;
  			len -= 2;
  		}
! 		else if (*from == SS3 && len >= 3)		/* JIS X 0212 KANJI */
  		{
  			from++;
! 			*to = (SS3 << 16) | (*from++ << 8);
! 			*to |= *from++;
  			len -= 3;
  		}
! 		else if ((*from & 0x80) && len >= 2)	/* JIS X 0208 KANJI */
  		{
  			*to = *from++ << 8;
  			*to |= *from++;
  			len -= 2;
  		}
! 		else	/* must be ASCII */
  		{
  			*to = *from++;
  			len--;
--
Tatsuo Ishii
SRA OSS, Inc. Japan



pgsql-jp メーリングリストの案内