From: Yusuke Endoh Date: 2010-04-13T22:48:39+09:00 Subject: [ruby-dev:40975] [Bug #3141] yield in an eigenclass definition Bug #3141: yield in an eigenclass definition http://redmine.ruby-lang.org/issues/show/3141 起票者: Yusuke Endoh ステータス: Open, 優先度: Normal 担当者: Yusuke Endoh, カテゴリ: core, Target version: 1.9.2 ruby -v: ruby 1.9.2dev (2010-04-13 trunk 27329) [i686-linux] ささださん 遠藤です。 #1018 に関連しますが、特異クラス定義の中で yield ができません。 class Object def yield_eigenclass class << self yield self end end end Object.yield_eigenclass {|c| p c } $ ruby19 t.rb t.rb:4: Invalid yield t.rb: compile error (SyntaxError) 調べていて知ったのですが、dfp[0] には - ISEQ_TYPE_METHOD, _CFUNC, _FINISH, _TOP の場合は rb_block_t* - それ以外の場合は前のフレームの dfp (vm_throw で使う) が入っているんですね (なんという dirty hack ……) 。 vm_push_frame の 5 番目の引数 (specval) に渡される値はずっと謎 でした。 ISEQ_TYPE_CLASS の dfp[0] には前のフレームの dfp が入っています。 (ブロックへのポインタと区別できるよう、下から 2 ビット目を立てて おくという、さらなる dirty hack (RUBY_VM_CLASS_SPECIAL_P) を見て 目がつぶれました) 一方で、ISEQ_TYPE_CLASS 以下の文脈からはブロックへの参照がない 状態になっていると思いました。ここまで、将来の自分のためのメモ。 そこで、ISEQ_TYPE_CLASS の dfp[0] にも rb_block_t* を入れることに しませんか。 メリット: - 上記のコードが動く - RUBY_VM_CLASS_SPECIAL_P の dirty hack が不要になる デメリット: - vm_throw で分岐が増える場合がある デメリットについては、break で以下を抜けるごとに 1 回分岐が増え ます。 - クラス定義 1.times { class C; break; end } - rescue 節 1.times { begin; ...; rescue; break; end } - ensure 節 1.times { begin; ...; ensure; break; end } ここにクラス定義が来ることは非常にレアですし、rescue 節や ensure 節をネストすることもレアだと思うので、問題にならないんじゃない かな、と思います。 というわけでパッチです。#1018 の wanabe さんのパッチも含みます。 make test-rubyspec と make check で特にエラーが増えない感じな ことは確認しています。コミットしてもいいでしょうか。 diff --git a/compile.c b/compile.c index 64411f9..7748fca 100644 --- a/compile.c +++ b/compile.c @@ -4229,7 +4229,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) rb_iseq_t *is = iseq; if (is) { - if (is->type == ISEQ_TYPE_TOP || is->type == ISEQ_TYPE_CLASS) { + if (is->type == ISEQ_TYPE_TOP) { COMPILE_ERROR((ERROR_ARGS "Invalid return")); } else { @@ -4269,7 +4269,7 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) unsigned long flag = 0; INIT_ANCHOR(args); - if (iseq->type == ISEQ_TYPE_TOP || iseq->type == ISEQ_TYPE_CLASS) { + if (iseq->type == ISEQ_TYPE_TOP) { COMPILE_ERROR((ERROR_ARGS "Invalid yield")); } diff --git a/insns.def b/insns.def index 9541465..14485f2 100644 --- a/insns.def +++ b/insns.def @@ -953,7 +953,7 @@ defineclass /* enter scope */ vm_push_frame(th, class_iseq, - VM_FRAME_MAGIC_CLASS, klass, (VALUE) GET_DFP() | 0x02, + VM_FRAME_MAGIC_CLASS, klass, (VALUE) GET_BLOCK_PTR(), class_iseq->iseq_encoded, GET_SP(), 0, class_iseq->local_size); RESTORE_REGS(); diff --git a/proc.c b/proc.c index eff7a0b..5fa46f1 100644 --- a/proc.c +++ b/proc.c @@ -374,16 +374,14 @@ proc_new(VALUE klass, int is_lambda) rb_control_frame_t *cfp = th->cfp; rb_block_t *block; - if ((GC_GUARDED_PTR_REF(cfp->lfp[0])) != 0 && - !RUBY_VM_CLASS_SPECIAL_P(cfp->lfp[0])) { + if ((GC_GUARDED_PTR_REF(cfp->lfp[0])) != 0) { block = GC_GUARDED_PTR_REF(cfp->lfp[0]); } else { cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - if ((GC_GUARDED_PTR_REF(cfp->lfp[0])) != 0 && - !RUBY_VM_CLASS_SPECIAL_P(cfp->lfp[0])) { + if ((GC_GUARDED_PTR_REF(cfp->lfp[0])) != 0) { block = GC_GUARDED_PTR_REF(cfp->lfp[0]); diff --git a/vm.c b/vm.c index 678fcb8..e5de8ac 100644 --- a/vm.c +++ b/vm.c @@ -477,7 +477,6 @@ rb_vm_make_proc(rb_thread_t *th, const rb_block_t *block, VALUE klass) } if (GC_GUARDED_PTR_REF(cfp->lfp[0])) { - if (!RUBY_VM_CLASS_SPECIAL_P(cfp->lfp[0])) { rb_proc_t *p; blockprocval = vm_make_proc_from_block( @@ -486,7 +485,6 @@ rb_vm_make_proc(rb_thread_t *th, const rb_block_t *block, VALUE klass) GetProcPtr(blockprocval, p); *cfp->lfp = GC_GUARDED_PTR(&p->block); } - } envval = rb_vm_make_env_object(th, cfp); diff --git a/vm_core.h b/vm_core.h index b5c1bf0..7ee1c1c 100644 --- a/vm_core.h +++ b/vm_core.h @@ -592,8 +592,6 @@ typedef rb_control_frame_t * #define RUBY_VM_NORMAL_ISEQ_P(ptr) \ (ptr && !RUBY_VM_IFUNC_P(ptr)) -#define RUBY_VM_CLASS_SPECIAL_P(ptr) (((VALUE)(ptr)) & 0x02) - #define RUBY_VM_GET_BLOCK_PTR_IN_CFP(cfp) ((rb_block_t *)(&(cfp)->self)) #define RUBY_VM_GET_CFP_FROM_BLOCK_PTR(b) \ ((rb_control_frame_t *)((VALUE *)(b) - 5)) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index cd74c8e..ae28ef3 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -890,8 +890,9 @@ vm_invoke_block(rb_thread_t *th, rb_control_frame_t *reg_cfp, rb_num_t num, rb_n const rb_block_t *block = GET_BLOCK_PTR(); rb_iseq_t *iseq; int argc = (int)num; + int type = GET_ISEQ()->local_iseq->type; - if (GET_ISEQ()->local_iseq->type != ISEQ_TYPE_METHOD || block == 0) { + if ((type != ISEQ_TYPE_METHOD && type != ISEQ_TYPE_CLASS) || block == 0) { rb_vm_localjump_error("no block given (yield)", Qnil, 0); } iseq = block->iseq; @@ -1411,6 +1412,11 @@ vm_throw(rb_thread_t *th, rb_control_frame_t *reg_cfp, search_parent: if (cfp->iseq->type != ISEQ_TYPE_BLOCK) { + if (cfp->iseq->type == ISEQ_TYPE_CLASS) { + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + dfp = cfp->dfp; + goto search_parent; + } dfp = GC_GUARDED_PTR_REF((VALUE *) *dfp); base_iseq = base_iseq->parent_iseq; @@ -1476,10 +1482,17 @@ vm_throw(rb_thread_t *th, rb_control_frame_t *reg_cfp, else if (state == TAG_RETURN) { rb_control_frame_t *cfp = GET_CFP(); VALUE *dfp = GET_DFP(); - VALUE * const lfp = GET_LFP(); + VALUE *lfp = GET_LFP(); /* check orphan and get dfp */ while ((VALUE *) cfp < th->stack + th->stack_size) { + if (!lfp) { + lfp = cfp->lfp; + } + if (cfp->dfp == lfp && cfp->iseq->type == ISEQ_TYPE_CLASS) { + lfp = 0; + } + if (cfp->lfp == lfp) { if (VM_FRAME_TYPE(cfp) == VM_FRAME_MAGIC_LAMBDA) { VALUE *tdfp = dfp; -- Yusuke Endoh ---------------------------------------- http://redmine.ruby-lang.org