Bug #5801
Updated by nobu (Nobuyoshi Nakada) over 9 years ago
芝と申します。 下の再現コードのように、Enumerable#take_while の Proc を 外に出して使うと Segv が発生します。 # 再現コード ~~~ruby =begin class A include Enumerable def each(&b) $block = b yield end end puts A.new.take_while{true} 100.times{$block.call} ~~~ `$block` には、`take_while` =end $block には、take_while 内で使用する `take_while_i` take_while_i (enum.c) が C の Proc として入ります。この Proc を上の例のように グローバル変数などに入れて、`take_while` グローバル変数などに入れて、take_while の呼出し後に使用 すると Segv を吐きます。 原因はおそらく、`enum_take_while` 原因はおそらく、enum_take_while と `take_while_i` take_while_i で、 `take_while` take_while の返り値である配列オブジェクトをポインタ経由で 渡していることではないでしょうか。 /* enum_take_while と take_while_i */ static VALUE enum_take_while(VALUE obj) { VALUE ary; RETURN_ENUMERATOR(obj, 0, 0); ary = rb_ary_new(); rb_block_call(obj, id_each, 0, 0, take_while_i, (VALUE)&ary /* <= ココ */); return ary; } static VALUE take_while_i(VALUE i, VALUE *ary /* <= ココ */, int argc, VALUE *argv) { if (!RTEST(enum_yield(argc, argv))) rb_iter_break(); rb_ary_push(*ary /* <= ココ */, enum_values_pack(argc, argv)); return Qnil; } enum_take_while では rb_block_call を呼び出すときに &ary を 引数として渡していますが、ary は C のローカル変数であるため、 C のスタックを指すことになります。このため、take_while の 呼出し後にグローバル変数などに保持された Proc から take_while_i が呼び出されると、*ary の値が書き換わってしまっていて Segv を 吐いているように見えます。 以下のパッチでこのバグが再現しないことは確認できました。 参考にしていただければ幸いです。 よろしくお願いいたします。 /* パッチ */ Index: enum.c =================================================================== --- enum.c (revision 34107) +++ enum.c (working copy) @@ -2053,7 +2053,7 @@ take_while_i(VALUE i, VALUE *ary, int argc, VALUE *argv) { if (!RTEST(enum_yield(argc, argv))) rb_iter_break(); - rb_ary_push(*ary, enum_values_pack(argc, argv)); + rb_ary_push((VALUE)ary, enum_values_pack(argc, argv)); return Qnil; } @@ -2079,7 +2079,7 @@ RETURN_ENUMERATOR(obj, 0, 0); ary = rb_ary_new(); - rb_block_call(obj, id_each, 0, 0, take_while_i, (VALUE)&ary); + rb_block_call(obj, id_each, 0, 0, take_while_i, ary); return ary; }