Project

General

Profile

Bug #5801

Updated by nobu (Nobuyoshi Nakada) over 9 years ago

芝と申します。 

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

 ~~~ruby 
 # 再現コード 
 ~~~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` 


 enum_take_while では `rb_block_call` rb_block_call を呼び出すときに `&ary` &ary引数として渡していますが、`ary` 引数として渡していますが、ary は C のローカル変数であるため、 
 C のスタックを指すことになります。このため、`take_while` のスタックを指すことになります。このため、take_while の 
 呼出し後にグローバル変数などに保持された Proc から `take_while_i` take_while_i 
 が呼び出されると、`*ary` が呼び出されると、*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; 
  } 
 ~~~

Back