check_kanji() と \kcatcode の範囲チェック

UTF-8対応(5) に書き込みのあった話題ですが、 分量が多くなって事実関係がわかり難くなったので、 ページを独立させてまとめなおしてみました。

本来は \kcatcode や \catcode の文字コードの範囲チェックがゆるいという 指摘を ZR 氏にいただいたのですが、 "procedure scan_char_num;" に出てくる check_kanji() 関数の処理に 私(土村)が疑問をはさんだために、 かなり独立した話題が並行して進められて、読みにくくなっています。

そこでここでは、先に check_kanji() の問題を解決し、 それから \kcatcode 等のチェックを強化することにします。

check_kanji() の問題

ptex-src の kanji.c には、 *1

boolean check_kanji(integer c);

という関数があります。

boolean は kpathsea で int に、 integer は Web2C で 32bit 以上の符号つき整数(long か int)に、 それぞれ typedef されています。

しかしながら、check_kanji() には次のような二つの問題があります。 (大きな変更をせず、既存の関数の改造によって 機能拡張が行われたのだと想像します。)

  1. boolean で宣言されているのにもかかわらず、3通りの値を返します。
  2. 関数名から想像されれるものとは異なる動作を行います。 (ASCII 文字にも true を返します。)

check_kanji(c) の返り値は次のようになっています。

 1 .. c:漢字
-1 .. c:ASCII without catcode (0〜255 or 256)
 0 .. c:ASCII with catcode (漢字でも 0〜255 でも 256 でもないもの)

そこで、この関数を3つの関数に分離することを提案してみます。 c=256 の場合は例外として個別に対処することにして、 0<=c<=255 の場合を ascii として名前をつけてみます。

boolean is_kanji(integer c)       { return (iskanji1(Hi(c)) && iskanji2(Lo(c))); }
boolean is_char_ascii(integer c)  { return (0 <= c && c < 0x100); }
boolean is_wchar_ascii(integer c) { return (!is_char_ascii(c) && !is_kanji(c)); }

これらの3つの関数を用いると、 check_kanji() は次のように書き直せます。 (最終的には Web2C、つまりは Pascal での表記に変形してみます。)

    check_kanji(c) <=> check_kanji(c) = 1 か -1
                   <=> c=0〜255 か 256 か 漢字
                   <=> is_kanji(c) or is_char_ascii(c) or c=256

not check_kanji(c) <=> (not is_kanji(c)) and (not is_char_ascii(c)) and c<>256
                   <=> is_wchar_ascii(c) and c<>256

check_kanji(c)>0   <=> is_kanji(c)

しかしながら、このように厳密に書き直したところで、 使われている場面によっては成立しない条件が含まれていて、 もっと簡略化できるところもあります。

簡略化

簡略化を正しく行うためには、pTeX の内部処理に深く精通している必要があります。 そこでここでは、どのように簡略化すべきか、 複数の人の目でチェックすることにより、信頼性を確保したいと思います。

以前の書き込み から利用させていただくと、 ptex-base.ch で用いられる check_kanji() は次のようになります。

 (1)  @<Display token |p|...@>                      [wchar_token] [check_kanji]
 (2)  @<Pseudoprint the token list@>                [wchar_token] [check_kanji]
 (3)  @<Input from token list,                      [wchar_token] [check_kanji]
 (4)  @<Look up the characters of list |r|          [wchar_token] [check_kanji]
 (5)  @ @<Fetch a character code from some table@>  [char_code]   [check_kanji>0]
 (6)  procedure scan_char_num                       [char_code]   [not check_kanji]
 (7)  @<Change the case of the token in |p|         [wchar_token] [not check_kanji]
 (8)  set_kansuji_char:                             [char_code]   [not check_kanji]
 (9)  assign_inhibit_xsp_code:                      [char_code]   [check_kanji]
 (10) assign_kinsoku:                               [char_code]   [check_kanji]

この中で、(5) の check_kanji(c)>0 は is_kanji(c) と等価です。 しかしながらその他の check_kanji(c) を is_kanji(c) に置き換えてよいかどうかは、 明らかではありません。 not check_kanji(c) も is_wchar_ascii(c) と置き換えられるとも限りません。 機械的に書き換えれば c=256 も出現します。 以下に書き換えバターンをまとめてみます。

番号種別古い表記新しい表記省略したもの確認
(1〜4)wchar_tokencheck_kanji(c)is_kanji(c)or is_char_ascii(c)
or c=256
たぶん
(5)char_codecheck_kanji(c)>0is_kanji(c)--OK
(6)char_codenot check_kanji(c)is_wchar_ascii(c)?and c<>256直前の not is_char_ascii(c) 相当も不要?
(7)wchar_tokennot check_kanji(c)not is_kanji(c)and (not is_char_ascii(c))
and c<>256
たぶん
(8)char_codenot check_kanji(c)not is_kanji(c)?and (not is_char_ascii(c))
and c<>256
?
(9)char_codecheck_kanji(c)is_kanji(c)?or is_char_ascii(c)
or c=256
?
(10)char_codecheck_kanji(c)is_char_ascii(c)
or is_kanji(c)
or c=256?

is_wchar_ascii(c) の出現頻度が少なければ、これは廃止して、 is_char_ascii(c) と is_kanji(c) とで展開して書けばよいと思います。


  • (6)のscan_char_numは前述の\kcatcode入力がらみの他にも\char, \chardefの入力にも使われています。全体で!(is_char_ascii(c) or is_kanji(c))になるべきでしょう。「直前の not is_char_ascii(c) 相当も不要?」というのが良く分からないのですが。is_wchar_ascii(c)はis_char_ascii(c)とis_kanji(c)の二つで書けるし、(6)と(10)だけなので不要だと思います。その他、check_kanjiでは書いていないが、(0<=cur_chr)and(cur_chr<256)というのが頻出しており、\char や\chardef などで和文と欧文を分離するのに使われているようです。これもis_char_aschiiに置き換えるとスッキリすると思います。upTeXでは、\char に絡んでいるそのあたりの部分はwebの関数にしようと思っています。 (---と、ここまで書き込んで、downloadのところをチェックしたら、もうほぼその通りの作業をなさっていらっしゃいました。 [smile]) -- ttk 2007-05-15 (火) 22:29:38
  • is_char_aschii() はすでに多用しています。is_wchar_ascii() は廃止の方向で考えます。(6) の「..相当も不要?」というのは is_wchar_ascii() を使った場合のことです。 -- 土村 2007-05-16 (水) 14:46:47
  • 関数名はもう少し考えねばならないと思います。ascii というと 7bit 文字を想像する人も多いでしょう。isascii() という関数も(ANSI ではありませんが)BSD 拡張で用意されていて、7bit 文字の判定をしているようです。is_kanji も iskanji1/iskanji2 と酷似していますし。 -- 土村 2007-05-16 (水) 14:50:58

\kcatcode 等の範囲チェック

上の問題が解決したら、\kcatcode 等の範囲チェックも強化したいと思います。



以前の書き込み

UTF-8対応(5) に書き込んで頂いた内容を、ここに移動しておきます。

  • もう一つの疑問です。\catcode の扱いについては奥村先生掲示板で読みました。 これに関してですが、"\catcode<漢字コード>" や "\kcatcode<1バイト整数>" の読み書き(勿論無意味な操作である)の仕様はどうなるのでしょうか。 pTeX の従来の挙動はかなり不可解で、 もう少し安全なものにしてもよいかと思います。 特に、"\catcode`あ=0" 等はメモリデータを破壊するようにみえます。-- 2007-05-03 (木) 13:26:20
  • すいません、細かいことは何もわかってないので、以下のソースを platex に処理させて、valgrind というメモリリーク監視ツールで観察してみました。変数の初期化忘れを一ヶ所だけ発見しましたが、おっしゃるような問題のありそうな処理は発見できませんでした。もし状況を把握されているのでしたら、問題を再現できるTeXソースか、それとも ptex-base.ch (?)の該当部分をお教えいただけるとありがたいです。
    \documentclass{jarticle}
    \catcode`あ=0
    \begin{document}
    \noindent
    あ=0 \newline
    亜=0
    \end{document}
    未初期化変数をなくすには、以下のようにすればよいでしょうか。 k を 0 で初期化すればよいのかどうか、あまり自信はありません。
    --- ptex-base.ch~       2007-05-04 00:55:18.000000000 +0900
    +++ ptex-base.ch        2007-05-04 02:27:31.000000000 +0900
    @@ -7160,7 +7160,7 @@
       cx:KANJI_code; {temporaly register for KANJI character}
       ax:ASCII_code; {temporaly register for ASCII character}
       do_ins:boolean; {for inserting |xkanji_skip| into prevous (or after) KANJI}
    -begin if link(p)=null then goto exit;
    +begin k:=0; if link(p)=null then goto exit;
     if auto_spacing>0 then
       begin delete_glue_ref(space_ptr(p)); space_ptr(p):=kanji_skip;
       add_glue_ref(kanji_skip);

    -- 土村 2007-05-04 (金) 02:48:51

  • tex/ptex のソースはほとんど読めていないのですが…。 次の plain pTeX のソース
    \catcode"ED=6
    \immediate\write16{1:\the\catcode"EDCA}
    \immediate\write16{2:\the\kcatcode`a}
    \catcode256=10
    \immediate\write16{3:\the\kcatcode`a}
    \bye
    を ptex (p3.1.10,euc) で処理した時の端末出力(一部)は
    (./test.tex
    1:6
    2:18
    3:10
    )
    となります。まず 1: から \catcode"xxyy を読みだすと \catcode"xx が返ることがわかります。 次に 2: と 3: ですが、現在の \kcatcode"xxyy は読み書きとも kcatcode の "xx 番目(0-origin)を指すはずです。 そして、catcode の「256 番目」が実際には kcatcode の 0 番目を指すため、 \kcatcode`a がいつの間にか変わってしまったのだと考えられます。

    -- ZR 2007-05-04 (金) 04:51:47

  • ptex-base.ch (p3.1.10 原版)の中の \catcode 等の書き込みをしている(と思われる)箇所です。
     p:=cur_chr; scan_char_num;
     if p=kcat_code_base then p:=p+Hi(cur_val) else p:=p+cur_val;
    ([49.1232] の @y ブロック) \catcode256 への書き込みでは、1 行目の終わりで p = cat_code_base, cur_val = 256 となってしまいます。

    -- ZR 2007-05-04 (金) 05:36:59

  • 確かに、チェックはゆるいですね。簡単にチェックするなら次の6行を加えればよいでしょうか。
    --- ptex-base.ch~       2007-05-04 15:09:01.000000000 +0900
    +++ ptex-base.ch        2007-05-04 16:21:22.000000000 +0900
    @@ -6006,6 +6006,12 @@
       @<Let |m| be the minimal legal code value, based on |cur_chr|@>;
       @<Let |n| be the largest legal code value, based on |cur_chr|@>;
       p:=cur_chr; scan_char_num;
    +  if (cur_val>=0)and(cur_val<256)and(p=kcat_code_base)then
    +     begin print_err("Invalid kcatcode (");
    +     print_int(cur_val);print(")"); error; end;
    +  if (cur_val>=256)and(p<>kcat_code_base)then
    +     begin print_err("Invalid catcode (");
    +     print_int(cur_val);print(")"); error; end;
       if p=kcat_code_base then p:=p+kcatcodekey(cur_val) else p:=p+cur_val;
       scan_optional_equals; scan_int;
       if ((cur_val<m)and(p<del_code_base))or(cur_val>n) then
    本来ならもっと単純に書けるはずなんですが、Web2c の if の入れ子がよくわからなくて。 ただ、ソースを見ていると、 "if (cur_chr>=0)and(cur_chr<256) then" というフレーズが頻出するので、 なんとかならないのかなぁというのと、 kanji.c の check_kanji() が 256 の時もアスキー文字扱いしているのが気になります。

    -- 土村 2007-05-04 (金) 16:38:00

  • 今の一番の問題は \catcode<漢字コード> の書き込みで不測のメモリ書き込みが起こることで、 私の理解する限りでは、値の検査はこれでいいと思います。 ただし、"invalid" なのは catcode や kcatcode ではなく 「文字コード」のはずです。 さらに、このままではユーザがエラーを Enter で突っ切ると、 結局メモリ書き込みがそのまま行われます。 scan_char_num 中の処理が参考になると思われます。 あと、過去との互換性の問題で、 不正のときに「エラーを出す」のと「黙って無視する」 のどちらが適当かという問題もあります。

    -- ZR 2007-05-04 (金) 20:03:10

  • \catcode256=10で\kcatcode表の0番目が書き換わってしまうというお話ですが、オリジナルpTeXの場合は少なくとも\kcatcode表の0x81番目以上、ptetex3の現在のヴァージョンでも0x21番目以上しか読み出す可能性は無く、和文の組版に際し\kcatcode表の0番目を参照する可能性はゼロであり、危険性は限りなくゼロに近いように思います(せいぜい誤入力にエラーを出せないことぐらい)。kanji.c の check_kanji() が 256 の時もアスキー文字扱いしている点は、欧文TeXで「それ以上展開できないコントロールシーケンス」がcur_val=256として例外的に扱われているのと、pTeXの和文はcur_val>=0x8140であるのをふまえていると想像しています。しかし、check_kanjiを呼ぶ時点でcur_val=256という例外的なケースを扱う可能性はゼロのような気もしています。この辺が正しく理解できないことにはupTeXも進まないのですが(汗)。 -- ttk 2007-05-06 (日) 22:41:47
  • 説明不足ですみません。 \catcode256 は破壊的代入の実証の為のみの例であり、 私も実害はないと思います。 問題なのは、\catcode`<和文文字> への代入です。 問題の個所のソース(ptetex3 最新)を再掲します。
     p:=cur_chr; scan_char_num;
     if p=kcat_code_base then p:=p+kcatcodekey(cur_val) else p:=p+cur_val;
     scan_optional_equals; scan_int;
     ....
     if p<math_code_base then define(p,data,cur_val)
     else if p<del_code_base then define(p,data,hi(cur_val))
     else word_define(p,cur_val);
     end;
    \catcode や \kcatcode 等の値は、プログラム中では配列 eqtb[] の中に格納されていて、例えば、
     \catcode<c>   の対応エントリは eqtb[cat_code_base+c]
     \kcatcode<k>  の対応エントリは eqtb[kcat_code_base+kcatcodekey(k)]
     (c は 1 バイト, k は漢字コード; kcat_code_base = cat_codebase+256)
    となっています。 (eqtb のエントリは複雑な構造をしていますが、 とにかくその値の一部のはずです。) そして、上述コード中の define(p,..) は eqtb[p] を書き換えます。 従って、\catcode256=10 では eqtb[cat_code_base+256]、 すなわち eqtb[kcat_code_base+0] が書き換えられました。 そして \catcode`あ=10 とすると、eqtb[cat_code_base+@"2422] を書き換えますが、この添字は eqtb の要素数を超えているので、 不測の結果を起こすはずです。 -- ZR 2007-05-07 (月) 01:14:46
  • もし、(無意味な操作の)結果の互換性を気にしないなら、 「文字コードを表す整数を読む」ルーチン scan_char_num を ASCII 用と漢字用に分ける (もし利用価値があるなら従来のものも残す) のが一番よいと考えています。 いずれにしても、TeX ソース中で "256" が文字コードを表すことはないので、 scan_char_num は 256 を拒否するべきでしょう。 (勿論 tex の scan_char_num は 0..255 しか受け付けない。) -- ZR 2007-05-07 (月) 02:15:48
  • なるほど、「\catcode`あ=10」はまずそうですね。ソースをよくよく追いかけてみると、オリジナルpTeXで他にもまずそうなところがありました。「\kansujichar1=`a \kansuji1」はエラーも警告も無く異常な結果が得られました。\catcodeと同様に「\xspcode`あ=0」もまずそうです。入力のチェックは厳しくすべきというご意見に賛同します。 -- ttk 2007-05-07 (月) 23:08:09
  • int check_kanji(int c) とし、 check_kanji(x) ---> check_kanji(x)>0, not check_kanji(x) ---> check_kanji(x)<=0 とすると、どこかでまずいことになるでしょうか? -- kakuto 2007-05-08 (火) 17:09:13
  • 失礼しました。これでは明らかにまずいようです。 -- kakuto 2007-05-08 (火) 19:41:37
  • check_kanji周りを調査しました。check_kanjiはptex-base.chの中で10回ほど出てきます。欧文がcatcode付のものとの比較になっているケース[wchar_token]と、catcodeの付かない裸の文字コードを扱うケース[char_code]の両方があるようです。問題が発生しているのは[char_code]の方です。まとめると、
     (1)  @<Display token |p|...@>                      [wchar_token]
     (2)  @<Pseudoprint the token list@>                [wchar_token]
     (3)  @<Input from token list,                      [wchar_token]
     (4)  @<Look up the characters of list |r|          [wchar_token]
     (5)  @ @<Fetch a character code from some table@>  [char_code] [ck>0] (*1)
     (6)  procedure scan_char_num                       [char_code] [not ck] (*2)
     (7)  @<Change the case of the token in |p|         [wchar_token]
     (8)  set_kansuji_char:                             [char_code] [not ck] (*3)
     (9)  assign_inhibit_xsp_code:                      [char_code] [ck] (*4)
     (10) assign_kinsoku:                               [char_code] [ck] (*5)
     *1 は現状維持が必須
     *2 は (not check_kanji(cur_val)) → (check_kanji(cur_val)<>1) がベター
     *3 は (not check_kanji(t)) → (check_kanji(t)<>1) がベター
     *4 は check_kanji(n) → check_kanji(n)>0 がベター
     *5 は例外的に ±1 の返り値を期待している。現状維持が必須
     check_kanji() でcatcodeがつかない欧文文字コードを受け付けるのは
     (10)のときだけで、c==256が正常入力されることはありえない。
     if (0 <= c && c <= 256) → if (0 <= c && c < 256) がベター
     check_kanji() の返り値のboolean, integerはどっちでも同じように見える。
    とりあえず、*2,*3,*4の変更を施せば、ZRさんご指摘の問題点のうち一部分は解消すると思います。\catcode`<和文>=<数> や \xspcode`<和文>=<数> の無警告オーバーランは直りませんが。以上の調査結果はそれほど自信があるわけではないのでご利用の際にはよくよく追試をお願いします。 -- ttk 2007-05-09 (水) 00:04:08
  • どこまで直すべきかの問題は保留して、現状把握をしておきます。
    • check_kanji() の仕様:
       check_kanji(c): c は文字コード値または文字トークン値
            1: c は有効な漢字コード
           -1: c は 0..256 の範囲
            0: それ以外
    • 戻り値が真理値か整数かで混乱が起きているように見える。 正しくは整数。 判定方法を >0, =0, <0 とするか =1, =0, =-1 とするかは要考慮。
    • 引数が文字トークン値である場合は、0 と -1 の区別は 意味を持たないので、ck=1 で判定すべき。
    • *2, *3, *4 は「漢字かどうか」の判定だから ck=1 を使うべき。 特に *2 の修正で、scan_char_num は 256 を拒否する。
    • *1 は「漢字か ASCII か」を判定している。 それ以外は scan_char_num で既に除外されている。
    • 最後に *5 ですが、これは \prebreakpenalty の処理であり、 この命令は漢字コードのみを引数にとるので、 私の観察では、これも ck=1 で判定すべきだと思う。
    • 私の観察がすべて正しければ、結局 check_kanji は「漢字か否か」の真理値を返せばよいことになるが…。

      -- ZR 2007-05-09 (水) 05:52:59

  • テーブルへの不正な書き込みを防ぐには、 例えば次のようにすればよいでしょう。
     @<Let |m| be the minimal legal code value, based on |cur_chr|@>;
     @<Let |n| be the largest legal code value, based on |cur_chr|@>;
     p:=cur_chr; scan_int; {*a}
     if p=kcat_code_base then { \kcatcode }
       if <cur_val が漢字コード> then p:=p+kcatcodekey(cur_val)
       else begin print_err(...); help1(...); error; end  {*b}
     else   { \catcode, \mathcode, \delcode 等 }
       if <cur_val が 0..255 の範囲> then p:=p+cur_val
       else begin print_err(...); help1(...); error; end; {*c}
     scan_optional_equals; scan_int;
     (注意)
     *a scan_char_num は「数値が 0..255 か漢字コードでない」と
        いうエラーを出すが、これは不適当。
     *b このエラーを突っ切ると、kcatcode の 0 番目に書き込むが、
        これは後の動作に影響しない。
     *c このエラーを突っ切ると、該当テーブルの 0 番目に書き込む。
        これは欧文 TeX の動作と同じ。

    -- ZR 2007-05-09 (水) 07:36:10

  • *5 の \prebreakpenalty, \postbreakpenalty は、例えば texmf/ptex/plain/base/kinsoku.tex の中に欧文8bitコードを引数に取った使用例があります。 -- ttk 2007-05-09 (水) 23:40:45
  • その通りでした。確かにこれが指定できないと困りますね。失礼しました。 -- ZR 2007-05-10 (木) 05:51:36

ご意見をどうぞ

このページ全般のご意見を下さい。 個別の話題には、それぞれ書き込む場所を用意しています。



*1 Web2C の *.web や *.ch で使われる関数は、同じ Web2C で記述するのが普通ですが、C言語で実装することも可能です。Cで書くと、なぜか関数名がアンダースコア "_" の除去されたもので参照されます。そのため、アスキーさんのオリジナルでは、Web2C では check_kanji() なのに、C言語ではcheckkanji() という関数名になっています。これでは grep をかけるときに不便です。ptetex3 で用意しているパッチでは少々変更して、kanji.c では check_kanji() で宣言した上で、kanji.h で "#define checkkanji check_kanji" として、Web2C と C言語での関数名を一致させています。

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-05-16 (水) 14:50:58 (3631d)