From: Yusuke ENDOH Date: 2009-05-21T22:54:26+09:00 Subject: [ruby-dev:38518] [Bug:1.9] Enumerator.new { }.take(1).inject(&:+) causes stack overflow 遠藤です。 以下のようにすると、謎の 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