From: ko1@... Date: 2014-06-19T12:40:54+00:00 Subject: [ruby-dev:48299] [ruby-trunk - Bug #9961] [Open] TracePoint can skip c_return with rb_rescue() Issue #9961 has been reported by Koichi Sasada. ---------------------------------------- Bug #9961: TracePoint can skip c_return with rb_rescue() https://bugs.ruby-lang.org/issues/9961 * Author: Koichi Sasada * Status: Open * Priority: Normal * Assignee: Koichi Sasada * Category: core * Target version: current: 2.2.0 * ruby -v: 2.2 * Backport: 2.0.0: REQUIRED, 2.1: REQUIRED ---------------------------------------- 下記のようなテストに失敗します。 ```ruby def test_rb_rescue events = [] curr_thread = Thread.current TracePoint.new(:a_call, :a_return){|tp| next if curr_thread != Thread.current events << [tp.event, tp.method_id] }.enable do begin -Numeric.new rescue => e # ignore end end assert_equal [ [:b_call, :test_rb_rescue], [:c_call, :new], [:c_call, :initialize], [:c_return, :initialize], [:c_return, :new], [:c_call, :-@], [:c_call, :coerce], [:c_call, :to_s], [:c_return, :to_s], [:c_call, :new], [:c_call, :initialize], [:c_return, :initialize], [:c_return, :new], [:c_call, :exception], [:c_return, :exception], [:c_call, :backtrace], [:c_return, :backtrace], [:c_return, :coerce], # don't miss it! [:c_call, :to_s], [:c_return, :to_s], [:c_call, :to_s], [:c_return, :to_s], [:c_call, :new], [:c_call, :initialize], [:c_return, :initialize], [:c_return, :new], [:c_call, :exception], [:c_return, :exception], [:c_call, :backtrace], [:c_return, :backtrace], [:c_return, :-@], [:c_call, :===], [:c_return, :===], [:b_return, :test_rb_rescue]], events end ``` 何が起きているかというと、 (1) -Numeric.new で rb_rescue() の後で rb_funcall() した先で例外が起こる (2) rb_rescue() では、c_return を無視して cfp を切り詰める ということにより、c_return がスキップしてしまう、という現象となりました。 次のパッチで解決します。 ```diff Index: eval.c =================================================================== --- eval.c (revision 46464) +++ eval.c (working copy) @@ -803,7 +803,7 @@ rb_rescue2(VALUE (* b_proc) (ANYARGS), V } } else { - th->cfp = cfp; /* restore */ + rb_vm_rewind_cfp(th, cfp); if (state == TAG_RAISE) { int handle = FALSE; @@ -862,7 +862,7 @@ rb_protect(VALUE (* proc) (VALUE), VALUE SAVE_ROOT_JMPBUF(th, result = (*proc) (data)); } else { - th->cfp = cfp; + rb_vm_rewind_cfp(th, cfp); } MEMCPY(&(th)->root_jmpbuf, &org_jmpbuf, rb_jmpbuf_t, 1); th->protect_tag = protect_tag.prev; Index: vm.c =================================================================== --- vm.c (revision 46464) +++ vm.c (working copy) @@ -288,6 +288,23 @@ rb_vm_pop_cfunc_frame(void) vm_pop_frame(th); } +void +rb_vm_rewind_cfp(rb_thread_t *th, rb_control_frame_t *cfp) +{ + /* check skipped frame */ + while (th->cfp != cfp) { +#if VMDEBUG + printf("skipped frame: %s\n", vm_frametype_name(th->cfp)); +#endif + if (VM_FRAME_TYPE(th->cfp) != VM_FRAME_MAGIC_CFUNC) { + vm_pop_frame(th); + } + else { /* unlikely path */ + rb_vm_pop_cfunc_frame(); + } + } +} + /* obsolete */ void rb_frame_pop(void) Index: vm_core.h =================================================================== --- vm_core.h (revision 46464) +++ vm_core.h (working copy) @@ -901,6 +901,7 @@ VALUE rb_name_err_mesg_new(VALUE obj, VA void rb_vm_stack_to_heap(rb_thread_t *th); void ruby_thread_init_stack(rb_thread_t *th); int rb_vm_control_frame_id_and_class(const rb_control_frame_t *cfp, ID *idp, VALUE *klassp); +void rb_vm_rewind_cfp(rb_thread_t *th, rb_control_frame_t *cfp); void rb_gc_mark_machine_stack(rb_thread_t *th); Index: vm_eval.c =================================================================== --- vm_eval.c (revision 46464) +++ vm_eval.c (working copy) @@ -1093,18 +1093,7 @@ rb_iterate(VALUE (* it_proc) (VALUE), VA th->errinfo = Qnil; retval = GET_THROWOBJ_VAL(err); - /* check skipped frame */ - while (th->cfp != cfp) { -#if VMDEBUG - printf("skipped frame: %s\n", vm_frametype_name(th->cfp)); -#endif - if (VM_FRAME_TYPE(th->cfp) != VM_FRAME_MAGIC_CFUNC) { - vm_pop_frame(th); - } - else { /* unlikely path */ - rb_vm_pop_cfunc_frame(); - } - } + rb_vm_rewind_cfp(th, cfp); } else{ /* SDR(); printf("%p, %p\n", cdfp, escape_dfp); */ ``` 例によって、2.1, 2.0 でもおきます。 (Numeric.new なんてできるとは知らなかった) -- https://bugs.ruby-lang.org/