[exerb-dev:0581] Re: Exerb で作成したプログラムの Integer.to_s で不具合

Back to archive index

MURASE Masamitsu masam****@gmail*****
2012年 4月 14日 (土) 05:20:07 JST


村瀬です。

久しぶりに Ruby 1.8.7 を Exe 化する必要がでてきたので調べてみたところ、本障害の原因は Ruby 1.8.7
のバグ、もしくは最適化の副作用によるもののように思います。

以下に、長いですが、詳細を書かせていただきます。


[現象]:
Bignum.to_s を呼び出している間の特定のタイミングでガーベジコレクションが発生すると、戻り値の文字列が不正になる。

[再現スクリプト]:
#= ここから =
num = 0x40000000
GC.stress = true
p num.to_s(16)
GC.stress = false
#= ここまで =

[再現スクリプトの実行結果]:
#= ここから =
"ae1590"
#= ここまで =
本来なら "40000000" と表示されるべきですが、変な文字列になっています。
文字列自体は、環境によって異なるかもしれません。

[修正方法]:
src/libruby18/src/bignum.c の 850行目付近に例えば以下の一行を追加すれば、正しく動作するようになります。
#= ここから =
	if (trim && ds[i-1] == 0) i--;
	k = SIZEOF_BDIGITS;
	while (k--) {
	    s[--j] = ruby_digitmap[num % base];
	    num /= base;
	    if (!trim && j <= 1) break;
	    if (trim && i == 0 && num == 0) break;
	}
    }
    t;  /** この「t;」の一行を追加 **/
    if (trim) {while (s[j] == '0') j++;}
    i = RSTRING(ss)->len - j;
    if (RBIGNUM(x)->sign) {
	memmove(s, s+j, i);
	RSTRING(ss)->len = i-1;
    }
    else {
#= ここまで =
この修正をしたファイルを添付しておきます。
(ベースは Exerb 5.4.0 です)

[推定原因]:
原因と思われる箇所は、bignum.c の 825行目付近の以下の部分です。
#= ここから =
    t = rb_big_clone(x);  /* (1) */
    ds = BDIGITS(t);       /* (2) */
    ss = rb_str_new(0, j+1);   /* (3) */
    s = RSTRING(ss)->ptr;
#= ここまで =
ここで行われているのは以下のことです。
上記の (1) の x には元々の Bignum (例では 0x40000000) が入っています。
その Bignum を volatile VALUE と宣言されている t にコピーしています。
Bignum は、巨大な数値を表すために、整数配列を内部に持っているようですが、その配列のアドレスを (2) で取得して ds に保存しています。
そして、(3) で Bignum を変換した文字列を格納するための文字列オブジェクトを作成しています。

障害は、(1) で x をクローンすることによって新規に生成された Bignum オブジェクト t が、(3)
内部でガーベジコレクションによって削除されてしまうことで、発生します。
ただし、これが起こるのは、
  ・上記 (3) 内部でガーベジコレクションが発生すること。
  ・ローカル変数 t, ds が同じメモリ番地に割り当てられること。
が必要です。
変数 t は、上記の (2) の右辺以降、この関数では登場しません。
そのため、コンパイラの最適化次第では、ds と同じメモリ番地に割り当てられます。
(私が VC2008 で試したときはそうでした。)
その結果、上記の (3) でガーベジコレクションが起こったとき、(1) で生成された Bignum
オブジェクトは、ローカルスタック上からは指し示されていないことになります。
よって、ガーベジコレクションの対象となり、削除され、ds が指しているメモリは回収された後のものとなり、不正なデータとなるようです。

Exerb でこれが起こったのは、コンパイラオプションで「サイズ優先最適化 /O1」を指定しているからだと思われます。
「速度優先 /O2」などに変更すれば、障害は発生しませんでした。

また、上で書きましたように、volatile 宣言されている t を、ds を参照し終わった後で参照してやるようにすることで、t と ds
が同じメモリ番地に割り当てられることもなくなり、ガーベジコレクション対象にならないようになるものと思います。

[お願い]:
私は VC6 をもっていないので、適当なパッチを当てて VC2008 で試しています。
可能であれば、どなたか VC6 で試していただけないでしょうか。

なお、これは Ruby 1.8.7 のバグのように思うので、別途、Ruby コミュニティに報告しておこうと思います。

最後になりましたが、とても有用なツールである Exerb を公開していただき、大変ありがとうございます。

長文失礼しました。不明点などありましたらお尋ねください。

以上、よろしくお願いします。
-- 
Mail:     masam****@gmail*****
Blog:    http://masamitsu-murase.blogspot.jp/
村瀬 昌満 (MURASE Masamitsu)
-------------- next part --------------
テキスト形式以外の添付ファイルを保管しました...
ファイル名: bignum.c
型:         text/x-csrc
サイズ:     49448 バイト
説明:       無し
下載 



exerb-developer メーリングリストの案内
Back to archive index