[前][次][番号順一覧][スレッド一覧]

ruby-changes:46148

From: ko1 <ko1@a...>
Date: Thu, 6 Apr 2017 11:56:28 +0900 (JST)
Subject: [ruby-changes:46148] ko1:r58262 (trunk): fix TracePoint#return_value with non-local exits

ko1	2017-04-06 11:56:23 +0900 (Thu, 06 Apr 2017)

  New Revision: 58262

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=58262

  Log:
    fix TracePoint#return_value with non-local exits
    
    * vm.c: get return_value from imemo_throw_data object (THROW_DATA_VAL()).
      imemo_throw_data (TAG_BREAK) contains returned value.
      However, imemo_throw_data (TAG_BREAK) can skip several frames so that
      we need to use it only once (at most internal frame). To record it,
      we introduced THROW_DATA_CONSUMED and check it.
    
    * internal.h: define THROW_DATA_CONSUMED flag.
    
    * test/ruby/test_settracefunc.rb: add tests for [Bug #13369]
    
    * vm_insnhelper.h: add THROW_DATA_CONSUMED_P() and
      THROW_DATA_CONSUMED_SET().

  Modified files:
    trunk/internal.h
    trunk/test/ruby/test_settracefunc.rb
    trunk/vm.c
    trunk/vm_insnhelper.h
Index: vm.c
===================================================================
--- vm.c	(revision 58261)
+++ vm.c	(revision 58262)
@@ -1599,29 +1599,69 @@ vm_frametype_name(const rb_control_frame https://github.com/ruby/ruby/blob/trunk/vm.c#L1599
 }
 #endif
 
+static VALUE
+frame_return_value(const struct vm_throw_data *err)
+{
+    if (THROW_DATA_P(err) &&
+	THROW_DATA_STATE(err) == TAG_BREAK &&
+	THROW_DATA_CONSUMED_P(err) == FALSE) {
+	return THROW_DATA_VAL(err);
+    }
+    else {
+	return Qnil;
+    }
+}
+
+#if 0
+/* for debug */
+static const char *
+frame_name(const rb_control_frame_t *cfp)
+{
+    unsigned long type = VM_FRAME_TYPE(cfp);
+#define C(t) if (type == VM_FRAME_MAGIC_##t) return #t
+    C(METHOD);
+    C(BLOCK);
+    C(CLASS);
+    C(TOP);
+    C(CFUNC);
+    C(PROC);
+    C(IFUNC);
+    C(EVAL);
+    C(LAMBDA);
+    C(RESCUE);
+    C(DUMMY);
+#undef C
+    return "unknown";
+}
+#endif
+
 static void
-hook_before_rewind(rb_thread_t *th, rb_control_frame_t *cfp, int will_finish_vm_exec)
+hook_before_rewind(rb_thread_t *th, const rb_control_frame_t *cfp, int will_finish_vm_exec, struct vm_throw_data *err)
 {
     switch (VM_FRAME_TYPE(th->cfp)) {
       case VM_FRAME_MAGIC_METHOD:
 	RUBY_DTRACE_METHOD_RETURN_HOOK(th, 0, 0);
-	EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_RETURN, th->cfp->self, 0, 0, 0, Qnil);
+	EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_RETURN, th->cfp->self, 0, 0, 0, frame_return_value(err));
+	THROW_DATA_CONSUMED_SET(err);
 	break;
       case VM_FRAME_MAGIC_BLOCK:
       case VM_FRAME_MAGIC_LAMBDA:
 	if (VM_FRAME_BMETHOD_P(th->cfp)) {
-	    EXEC_EVENT_HOOK(th, RUBY_EVENT_B_RETURN, th->cfp->self, 0, 0, 0, Qnil);
+	    EXEC_EVENT_HOOK(th, RUBY_EVENT_B_RETURN, th->cfp->self, 0, 0, 0, frame_return_value(err));
 
 	    if (!will_finish_vm_exec) {
 		/* kick RUBY_EVENT_RETURN at invoke_block_from_c() for bmethod */
 		EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_RETURN, th->cfp->self,
 					      rb_vm_frame_method_entry(th->cfp)->def->original_id,
 					      rb_vm_frame_method_entry(th->cfp)->called_id,
-					      rb_vm_frame_method_entry(th->cfp)->owner, Qnil);
+					      rb_vm_frame_method_entry(th->cfp)->owner,
+					      frame_return_value(err));
 	    }
+	    THROW_DATA_CONSUMED_SET(err);
 	}
 	else {
-	    EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_B_RETURN, th->cfp->self, 0, 0, 0, Qnil);
+	    EXEC_EVENT_HOOK_AND_POP_FRAME(th, RUBY_EVENT_B_RETURN, th->cfp->self, 0, 0, 0, frame_return_value(err));
+	    THROW_DATA_CONSUMED_SET(err);
 	}
 	break;
       case VM_FRAME_MAGIC_CLASS:
@@ -1784,10 +1824,11 @@ vm_exec(rb_thread_t *th) https://github.com/ruby/ruby/blob/trunk/vm.c#L1824
 				}
 			    }
 			}
-			if (!catch_iseq) {
+			if (catch_iseq == NULL) {
 			    th->errinfo = Qnil;
 			    result = THROW_DATA_VAL(err);
-			    hook_before_rewind(th, th->cfp, TRUE);
+			    THROW_DATA_CATCH_FRAME_SET(err, cfp + 1);
+			    hook_before_rewind(th, th->cfp, TRUE, err);
 			    rb_vm_pop_frame(th);
 			    goto finish_vme;
 			}
@@ -1929,8 +1970,7 @@ vm_exec(rb_thread_t *th) https://github.com/ruby/ruby/blob/trunk/vm.c#L1970
 	    goto vm_loop_start;
 	}
 	else {
-	    /* skip frame */
-	    hook_before_rewind(th, th->cfp, FALSE);
+	    hook_before_rewind(th, th->cfp, FALSE, err);
 
 	    if (VM_FRAME_FINISHED_P(th->cfp)) {
 		rb_vm_pop_frame(th);
Index: test/ruby/test_settracefunc.rb
===================================================================
--- test/ruby/test_settracefunc.rb	(revision 58261)
+++ test/ruby/test_settracefunc.rb	(revision 58262)
@@ -1599,4 +1599,146 @@ class TestSetTraceFunc < Test::Unit::Tes https://github.com/ruby/ruby/blob/trunk/test/ruby/test_settracefunc.rb#L1599
     assert_equal [[:c_call, :itself, :alias_itself], [:c_return, :itself, :alias_itself]], events
     events.clear
   end
+
+  # tests for `return_value` with non-local exit [Bug #13369]
+
+  def tp_return_value mid
+    ary = []
+    TracePoint.new(:return, :b_return){|tp| ary << [tp.event, tp.method_id, tp.return_value]}.enable{
+      send mid
+    }
+    ary.pop # last b_return event is not required.
+    ary
+  end
+
+  def f_raise
+    raise
+  rescue
+    return :f_raise_return
+  end
+
+  def f_iter1
+    yield
+    return :f_iter1_return
+  end
+
+  def f_iter2
+    yield
+    return :f_iter2_return
+  end
+
+  def f_return_in_iter
+    f_iter1 do
+      f_iter2 do
+        return :f_return_in_iter_return
+      end
+    end
+    2
+  end
+
+  def f_break_in_iter
+    f_iter1 do
+      f_iter2 do
+        break :f_break_in_iter_break
+      end
+      :f_iter1_block_value
+    end
+    :f_break_in_iter_return
+  end
+
+  def test_return_value_with_rescue
+    assert_equal [[:return,   :f_raise,          :f_raise_return]],
+                 tp_return_value(:f_raise),
+                 '[Bug #13369]'
+
+    assert_equal [[:b_return, :f_return_in_iter, nil],
+                  [:return,   :f_iter2,          nil],
+                  [:b_return, :f_return_in_iter, nil],
+                  [:return,   :f_iter1,          nil],
+                  [:return,   :f_return_in_iter, :f_return_in_iter_return]],
+                 tp_return_value(:f_return_in_iter),
+                 '[Bug #13369]'
+
+    assert_equal [[:b_return, :f_break_in_iter,  :f_break_in_iter_break],
+                  [:return,   :f_iter2,          nil],
+                  [:b_return, :f_break_in_iter,  :f_iter1_block_value],
+                  [:return,   :f_iter1,          :f_iter1_return],
+                  [:return,   :f_break_in_iter,  :f_break_in_iter_return]],
+                 tp_return_value(:f_break_in_iter),
+                 '[Bug #13369]'
+  end
+
+  define_method(:f_last_defined) do
+    :f_last_defined
+  end
+
+  define_method(:f_return_defined) do
+    return :f_return_defined
+  end
+
+  define_method(:f_break_defined) do
+    return :f_break_defined
+  end
+
+  define_method(:f_raise_defined) do
+    raise
+  rescue
+    return :f_raise_defined
+  end
+
+  define_method(:f_break_in_rescue_defined) do
+    raise
+  rescue
+    break :f_break_in_rescue_defined
+  end
+
+  def test_return_value_with_rescue_and_defined_methods
+    assert_equal [[:b_return, :f_last_defined, :f_last_defined],
+                  [:return,   :f_last_defined, :f_last_defined]],
+                 tp_return_value(:f_last_defined),
+                 '[Bug #13369]'
+
+    assert_equal [[:b_return, :f_return_defined, nil], # current limitation
+                  [:return,   :f_return_defined, :f_return_defined]],
+                 tp_return_value(:f_return_defined),
+                 '[Bug #13369]'
+
+    assert_equal [[:b_return, :f_break_defined, nil],
+                  [:return,   :f_break_defined, :f_break_defined]],
+                 tp_return_value(:f_break_defined),
+                 '[Bug #13369]'
+
+    assert_equal [[:b_return, :f_raise_defined, nil],
+                  [:return,   :f_raise_defined, f_raise_defined]],
+                 tp_return_value(:f_raise_defined),
+                 '[Bug #13369]'
+
+    assert_equal [[:b_return, :f_break_in_rescue_defined, nil],
+                  [:return,   :f_break_in_rescue_defined, f_break_in_rescue_defined]],
+                 tp_return_value(:f_break_in_rescue_defined),
+                 '[Bug #13369]'
+  end
+
+  def f_iter
+    yield
+  end
+
+  def f_break_in_rescue
+    f_iter do
+      begin
+        raise
+      rescue
+        break :b
+      end
+    end
+    :f_break_in_rescue_return_value
+  end
+
+  def test_break_with_rescue
+    assert_equal [[:b_return, :f_break_in_rescue, :b],
+                  [:return, :f_iter, nil],
+                  [:return, :f_break_in_rescue, :f_break_in_rescue_return_value]],
+                 tp_return_value(:f_break_in_rescue),
+                 '[Bug #13369]'
+  end
 end
Index: vm_insnhelper.h
===================================================================
--- vm_insnhelper.h	(revision 58261)
+++ vm_insnhelper.h	(revision 58262)
@@ -196,18 +196,6 @@ THROW_DATA_NEW(VALUE val, const rb_contr https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.h#L196
     return (struct vm_throw_data *)rb_imemo_new(imemo_throw_data, val, (VALUE)cf, st, 0);
 }
 
-static inline void
-THROW_DATA_CATCH_FRAME_SET(struct vm_throw_data *obj, const rb_control_frame_t *cfp)
-{
-    obj->catch_frame = cfp;
-}
-
-static inline void
-THROW_DATA_STATE_SET(struct vm_throw_data *obj, int st)
-{
-    obj->throw_state = (VALUE)st;
-}
-
 static inline VALUE
 THROW_DATA_VAL(const struct vm_throw_data *obj)
 {
@@ -220,10 +208,38 @@ THROW_DATA_CATCH_FRAME(const struct vm_t https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.h#L208
     return obj->catch_frame;
 }
 
-static int
+static inline int
 THROW_DATA_STATE(const struct vm_throw_data *obj)
 {
     return (int)obj->throw_state;
 }
 
+static inline int
+THROW_DATA_CONSUMED_P(const struct vm_throw_data *obj)
+{
+    VM_ASSERT(THROW_DATA_P(obj));
+    return obj->flags & THROW_DATA_CONSUMED;
+}
+
+static inline void
+THROW_DATA_CATCH_FRAME_SET(struct vm_throw_data *obj, const rb_control_frame_t *cfp)
+{
+    obj->catch_frame = cfp;
+}
+
+static inline void
+THROW_DATA_STATE_SET(struct vm_throw_data *obj, int st)
+{
+    obj->throw_state = (VALUE)st;
+}
+
+static inline void
+THROW_DATA_CONSUMED_SET(struct vm_throw_data *obj)
+{
+    if (THROW_DATA_P(obj) &&
+	THROW_DATA_STATE(obj) == TAG_BREAK) {
+	obj->flags |= THROW_DATA_CONSUMED;
+    }
+}
+
 #endif /* RUBY_INSNHELPER_H */
Index: internal.h
===================================================================
--- internal.h	(revision 58261)
+++ internal.h	(revision 58262)
@@ -877,6 +877,8 @@ struct vm_svar { https://github.com/ruby/ruby/blob/trunk/internal.h#L877
 
 /* THROW_DATA */
 
+#define THROW_DATA_CONSUMED IMEMO_FL_USER0
+
 struct vm_throw_data {
     VALUE flags;
     VALUE reserved;
@@ -885,7 +887,7 @@ struct vm_throw_data { https://github.com/ruby/ruby/blob/trunk/internal.h#L887
     VALUE throw_state;
 };
 
-#define THROW_DATA_P(err) RB_TYPE_P((err), T_IMEMO)
+#define THROW_DATA_P(err) RB_TYPE_P(((VALUE)err), T_IMEMO)
 
 /* IFUNC */
 

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]