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

ruby-changes:65729

From: Koichi <ko1@a...>
Date: Fri, 2 Apr 2021 09:25:51 +0900 (JST)
Subject: [ruby-changes:65729] ecfa8dcdba (master): fix return from orphan Proc in lambda

https://git.ruby-lang.org/ruby.git/commit/?id=ecfa8dcdba

From ecfa8dcdbaf60cbe878389439de9ac94bc82e034 Mon Sep 17 00:00:00 2001
From: Koichi Sasada <ko1@a...>
Date: Fri, 2 Apr 2021 02:28:00 +0900
Subject: fix return from orphan Proc in lambda

A "return" statement in a Proc in a lambda like:
  `lambda{ proc{ return }.call }`
should return outer lambda block. However, the inner Proc can become
orphan Proc from the lambda block. This "return" escape outer-scope
like method, but this behavior was decieded as a bug.
[Bug #17105]

This patch raises LocalJumpError by checking the proc is orphan or
not from lambda blocks before escaping by "return".

Most of tests are written by Jeremy Evans
https://github.com/ruby/ruby/pull/4223
---
 test/ruby/test_lambda.rb | 103 +++++++++++++++++++++++++++++++++++++++++++++++
 vm_insnhelper.c          |  38 +++++++++++++----
 2 files changed, 134 insertions(+), 7 deletions(-)

diff --git a/test/ruby/test_lambda.rb b/test/ruby/test_lambda.rb
index 8e46047..2c42b54 100644
--- a/test/ruby/test_lambda.rb
+++ b/test/ruby/test_lambda.rb
@@ -74,6 +74,109 @@ class TestLambdaParameters < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_lambda.rb#L74
     assert_raise(ArgumentError, bug9605) {proc(&plus).call [1,2]}
   end
 
+  def test_proc_inside_lambda_inside_method_return_inside_lambda_inside_method
+    def self.a
+      r = -> do
+        p = Proc.new{return :a}
+        p.call
+      end.call
+    end
+    assert_equal(:a, a)
+
+    def self.b
+      r = lambda do
+        p = Proc.new{return :b}
+        p.call
+      end.call
+    end
+    assert_equal(:b, b)
+  end
+
+  def test_proc_inside_lambda_inside_method_return_inside_lambda_outside_method
+    def self.a
+      r = -> do
+        p = Proc.new{return :a}
+        p.call
+      end
+    end
+    assert_equal(:a, a.call)
+
+    def self.b
+      r = lambda do
+        p = Proc.new{return :b}
+        p.call
+      end
+    end
+    assert_equal(:b, b.call)
+  end
+
+  def test_proc_inside_lambda_inside_method_return_outside_lambda_inside_method
+    def self.a
+      r = -> do
+        Proc.new{return :a}
+      end.call.call
+    end
+    assert_raise(LocalJumpError) {a}
+
+    def self.b
+      r = lambda do
+        Proc.new{return :b}
+      end.call.call
+    end
+    assert_raise(LocalJumpError) {b}
+  end
+
+  def test_proc_inside_lambda_inside_method_return_outside_lambda_outside_method
+    def self.a
+      r = -> do
+        Proc.new{return :a}
+      end
+    end
+    assert_raise(LocalJumpError) {a.call.call}
+
+    def self.b
+      r = lambda do
+        Proc.new{return :b}
+      end
+    end
+    assert_raise(LocalJumpError) {b.call.call}
+  end
+
+  def test_proc_inside_lambda2_inside_method_return_outside_lambda1_inside_method
+    def self.a
+      r = -> do
+        -> do
+          Proc.new{return :a}
+        end.call.call
+      end.call
+    end
+    assert_raise(LocalJumpError) {a}
+
+    def self.b
+      r = lambda do
+        lambda do
+          Proc.new{return :a}
+        end.call.call
+      end.call
+    end
+    assert_raise(LocalJumpError) {b}
+  end
+
+  def test_proc_inside_lambda_toplevel
+    assert_separately [], <<~RUBY
+      lambda{
+        $g = proc{ return :pr }
+      }.call
+      begin
+        $g.call
+      rescue LocalJumpError
+        # OK!
+      else
+        raise
+      end
+    RUBY
+  end
+
   def pass_along(&block)
     lambda(&block)
   end
diff --git a/vm_insnhelper.c b/vm_insnhelper.c
index 23b3c51..854b8a9 100644
--- a/vm_insnhelper.c
+++ b/vm_insnhelper.c
@@ -1390,13 +1390,22 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1390
     }
     else if (state == TAG_RETURN) {
 	const VALUE *current_ep = GET_EP();
-	const VALUE *target_lep = VM_EP_LEP(current_ep);
+        const VALUE *target_ep = NULL, *target_lep, *ep = current_ep;
 	int in_class_frame = 0;
 	int toplevel = 1;
 	escape_cfp = reg_cfp;
 
-	while (escape_cfp < eocfp) {
-	    const VALUE *lep = VM_CF_LEP(escape_cfp);
+        // find target_lep, target_ep
+        while (!VM_ENV_LOCAL_P(ep)) {
+            if (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_LAMBDA) && target_ep == NULL) {
+                target_ep = ep;
+            }
+            ep = VM_ENV_PREV_EP(ep);
+        }
+        target_lep = ep;
+
+        while (escape_cfp < eocfp) {
+            const VALUE *lep = VM_CF_LEP(escape_cfp);
 
 	    if (!target_lep) {
 		target_lep = lep;
@@ -1414,7 +1423,7 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1423
 		    toplevel = 0;
 		    if (in_class_frame) {
 			/* lambda {class A; ... return ...; end} */
-			goto valid_return;
+                        goto valid_return;
 		    }
 		    else {
 			const VALUE *tep = current_ep;
@@ -1422,7 +1431,12 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1431
 			while (target_lep != tep) {
 			    if (escape_cfp->ep == tep) {
 				/* in lambda */
-				goto valid_return;
+                                if (tep == target_ep) {
+                                    goto valid_return;
+                                }
+                                else {
+                                    goto unexpected_return;
+                                }
 			    }
 			    tep = VM_ENV_PREV_EP(tep);
 			}
@@ -1434,7 +1448,12 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1448
 		      case ISEQ_TYPE_MAIN:
                         if (toplevel) {
                             if (in_class_frame) goto unexpected_return;
-                            goto valid_return;
+                            if (target_ep == NULL) {
+                                goto valid_return;
+                            }
+                            else {
+                                goto unexpected_return;
+                            }
                         }
 			break;
 		      case ISEQ_TYPE_EVAL:
@@ -1448,7 +1467,12 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1467
 	    }
 
 	    if (escape_cfp->ep == target_lep && escape_cfp->iseq->body->type == ISEQ_TYPE_METHOD) {
-		goto valid_return;
+                if (target_ep == NULL) {
+                    goto valid_return;
+                }
+                else {
+                    goto unexpected_return;
+                }
 	    }
 
 	    escape_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(escape_cfp);
-- 
cgit v1.1


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

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