Issue #5801 has been updated by Nobuyoshi Nakada.

Description updated

----------------------------------------
Bug #5801: Enumerable#take_while の proc を外に出して使うと Segv
https://bugs.ruby-lang.org/issues/5801#change-52844

* Author: satoshi shiba
* Status: Closed
* Priority: Normal
* Assignee: 
* ruby -v: ruby 1.9.3p6 (2011-12-20 revision 34080) [i686-linux]
* Backport: 
----------------------------------------
芝と申します。

下の再現コードのように、`Enumerable#take_while` の Proc を
外に出して使うと Segv が発生します。

~~~ruby
# 再現コード
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` 内で使用する `take_while_i` (enum.c)
が C の Proc として入ります。この Proc を上の例のように
グローバル変数などに入れて、`take_while` の呼出し後に使用
すると Segv を吐きます。

原因はおそらく、`enum_take_while` と `take_while_i` で、
`take_while` の返り値である配列オブジェクトをポインタ経由で
渡していることではないでしょうか。

~~~c
/* 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 を
吐いているように見えます。

以下のパッチでこのバグが再現しないことは確認できました。
参考にしていただければ幸いです。
よろしくお願いいたします。

~~~diff
/* パッチ */
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;
 }
~~~



-- 
https://bugs.ruby-lang.org/