Bug #5801
closedEnumerable#take_while の proc を外に出して使うと Segv
Description
芝と申します。
下の再現コードのように、Enumerable#take_while
の Proc を
外に出して使うと Segv が発生します。
# 再現コード
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
の返り値である配列オブジェクトをポインタ経由で
渡していることではないでしょうか。
/* 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;
}
Updated by shiba (satoshi shiba) almost 13 years ago
Enumerable#grep なども同様の問題がありますね。
以下の再現コードで Segv を吐きます。
enum.c で、XX_i という形の関数の第二引数が C の
スタックへのポインタになってるやつは、全部同じ問題を
含んでいるのではないでしょうか。
再現コード¶
class A
include Enumerable
def each(&b)
$block = b
yield
end
end
puts A.new.grep(//)
100.times{$block.call}
static VALUE
grep_i(VALUE i, VALUE args, int argc, VALUE *argv)
{
VALUE *arg = (VALUE *)args; /* arg は C のスタックを指す */
ENUM_WANT_SVALUE();
if (RTEST(rb_funcall(arg[0], id_eqq, 1, i))) {
rb_ary_push(arg[1], i);
}
return Qnil;
}
Updated by nobu (Nobuyoshi Nakada) over 12 years ago
- Status changed from Open to Closed
- % Done changed from 0 to 100
This issue was solved with changeset r34661.
satoshi, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.
- enum.c: move work variables to objects not to let called blocks
access stack area out of scope. [Bug #5801]
Updated by nobu (Nobuyoshi Nakada) over 9 years ago
- Description updated (diff)
Updated by nobu (Nobuyoshi Nakada) over 9 years ago
- Description updated (diff)