Project

General

Profile

Bug #1501

Enumerator.new { }.take(1).inject(&:+) causes stack overflow

Added by mame (Yusuke Endoh) over 11 years ago. Updated over 9 years ago.

Status:
Closed
Priority:
Normal
Target version:
ruby -v:
ruby 1.9.2dev (2009-05-19 trunk 23489) [i686-linux]
Backport:
[ruby-dev:38518]

Description

=begin
遠藤です。

以下のようにすると、謎の SystemStackError が出てきます。

$ ./ruby -ve 'Enumerator.new { }.take(1).inject(&:+)'
ruby 1.9.2dev (2009-05-19 trunk 23489) [i686-linux]
-e:1:in proc': stack level too deep (SystemStackError)
from -e:1:in
to_proc'
from -e:1:in `'

1.9.1-p0 でも同じでした。

調べてみたところ、現在のブロックの cfp->lfp[0] が指すブロックが
自分自身になっているようです (正確には、他のブロックを解して
間接的に循環しているようでした) 。
そのせいで、vm.c の rb_vm_make_proc と vm_make_proc_from_block が
相互に呼び出しあって無限再帰しているようです。

cfp->lfp[0] がおかしくなる原因はたぶん enumerator.c で、

  • yielder_new が rb_iterate(yielder_new_i, ...) を呼ぶ
  • passed_block が設定される
  • yielder_new_i は Ruby レベルのメソッドを呼ばずに終了する
  • passed_block が設定されたまま、yielder_new が終わり、YARV の eval ループに戻る
  • passed_block がどこか変なところで cfp->lfp[0] に代入される

という流れになっているように感じました (yielder がどんなものかは
よくわかっていません) 。

そこで、以下のように、yielder_new_i で proc メソッドを呼ぶことで
バグは直りました。

Index: enumerator.c
===================================================================
--- enumerator.c (revision 23508)
+++ enumerator.c (working copy)
@@ -720,7 +720,7 @@
static VALUE
yielder_new_i(VALUE dummy)
{

  • return yielder_init(yielder_allocate(rb_cYielder), rb_block_proc());
  • return yielder_init(yielder_allocate(rb_cYielder), rb_funcall(Qnil, rb_intern("proc"), 0)); }

static VALUE

ささださんがいいと言ってくれたら (もしくは何も言わないなら)
コミットしようと思います。

上に関係して、Ruby のソースコードには以下のアサーションが暗黙に
存在すると思ったのですが、正しいでしょうか。

  • passed_block が設定されたら、eval ループに戻る前に Ruby レベルの
    メソッドを呼んで passed_block を回収させないといけない

  • rb_iterate や rb_block_call の第一引数に渡される関数は、その中で
    Ruby レベルのメソッドを呼ばないといけない

後者は C API に関わる話なので、正しいようなら README.EXT に書き
加えた方がいいと思います。というか書き加えようと思います。

--
Yusuke ENDOH mame@tsg.ne.jp
=end

Also available in: Atom PDF