Skip to content

Commit ecfa8dc

Browse files
committed
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 #4223
1 parent c080bb2 commit ecfa8dc

File tree

2 files changed

+134
-7
lines changed

2 files changed

+134
-7
lines changed

test/ruby/test_lambda.rb

+103
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,109 @@ def test_call_block_from_lambda
7474
assert_raise(ArgumentError, bug9605) {proc(&plus).call [1,2]}
7575
end
7676

77+
def test_proc_inside_lambda_inside_method_return_inside_lambda_inside_method
78+
def self.a
79+
r = -> do
80+
p = Proc.new{return :a}
81+
p.call
82+
end.call
83+
end
84+
assert_equal(:a, a)
85+
86+
def self.b
87+
r = lambda do
88+
p = Proc.new{return :b}
89+
p.call
90+
end.call
91+
end
92+
assert_equal(:b, b)
93+
end
94+
95+
def test_proc_inside_lambda_inside_method_return_inside_lambda_outside_method
96+
def self.a
97+
r = -> do
98+
p = Proc.new{return :a}
99+
p.call
100+
end
101+
end
102+
assert_equal(:a, a.call)
103+
104+
def self.b
105+
r = lambda do
106+
p = Proc.new{return :b}
107+
p.call
108+
end
109+
end
110+
assert_equal(:b, b.call)
111+
end
112+
113+
def test_proc_inside_lambda_inside_method_return_outside_lambda_inside_method
114+
def self.a
115+
r = -> do
116+
Proc.new{return :a}
117+
end.call.call
118+
end
119+
assert_raise(LocalJumpError) {a}
120+
121+
def self.b
122+
r = lambda do
123+
Proc.new{return :b}
124+
end.call.call
125+
end
126+
assert_raise(LocalJumpError) {b}
127+
end
128+
129+
def test_proc_inside_lambda_inside_method_return_outside_lambda_outside_method
130+
def self.a
131+
r = -> do
132+
Proc.new{return :a}
133+
end
134+
end
135+
assert_raise(LocalJumpError) {a.call.call}
136+
137+
def self.b
138+
r = lambda do
139+
Proc.new{return :b}
140+
end
141+
end
142+
assert_raise(LocalJumpError) {b.call.call}
143+
end
144+
145+
def test_proc_inside_lambda2_inside_method_return_outside_lambda1_inside_method
146+
def self.a
147+
r = -> do
148+
-> do
149+
Proc.new{return :a}
150+
end.call.call
151+
end.call
152+
end
153+
assert_raise(LocalJumpError) {a}
154+
155+
def self.b
156+
r = lambda do
157+
lambda do
158+
Proc.new{return :a}
159+
end.call.call
160+
end.call
161+
end
162+
assert_raise(LocalJumpError) {b}
163+
end
164+
165+
def test_proc_inside_lambda_toplevel
166+
assert_separately [], <<~RUBY
167+
lambda{
168+
$g = proc{ return :pr }
169+
}.call
170+
begin
171+
$g.call
172+
rescue LocalJumpError
173+
# OK!
174+
else
175+
raise
176+
end
177+
RUBY
178+
end
179+
77180
def pass_along(&block)
78181
lambda(&block)
79182
end

vm_insnhelper.c

+31-7
Original file line numberDiff line numberDiff line change
@@ -1390,13 +1390,22 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c
13901390
}
13911391
else if (state == TAG_RETURN) {
13921392
const VALUE *current_ep = GET_EP();
1393-
const VALUE *target_lep = VM_EP_LEP(current_ep);
1393+
const VALUE *target_ep = NULL, *target_lep, *ep = current_ep;
13941394
int in_class_frame = 0;
13951395
int toplevel = 1;
13961396
escape_cfp = reg_cfp;
13971397

1398-
while (escape_cfp < eocfp) {
1399-
const VALUE *lep = VM_CF_LEP(escape_cfp);
1398+
// find target_lep, target_ep
1399+
while (!VM_ENV_LOCAL_P(ep)) {
1400+
if (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_LAMBDA) && target_ep == NULL) {
1401+
target_ep = ep;
1402+
}
1403+
ep = VM_ENV_PREV_EP(ep);
1404+
}
1405+
target_lep = ep;
1406+
1407+
while (escape_cfp < eocfp) {
1408+
const VALUE *lep = VM_CF_LEP(escape_cfp);
14001409

14011410
if (!target_lep) {
14021411
target_lep = lep;
@@ -1414,15 +1423,20 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c
14141423
toplevel = 0;
14151424
if (in_class_frame) {
14161425
/* lambda {class A; ... return ...; end} */
1417-
goto valid_return;
1426+
goto valid_return;
14181427
}
14191428
else {
14201429
const VALUE *tep = current_ep;
14211430

14221431
while (target_lep != tep) {
14231432
if (escape_cfp->ep == tep) {
14241433
/* in lambda */
1425-
goto valid_return;
1434+
if (tep == target_ep) {
1435+
goto valid_return;
1436+
}
1437+
else {
1438+
goto unexpected_return;
1439+
}
14261440
}
14271441
tep = VM_ENV_PREV_EP(tep);
14281442
}
@@ -1434,7 +1448,12 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c
14341448
case ISEQ_TYPE_MAIN:
14351449
if (toplevel) {
14361450
if (in_class_frame) goto unexpected_return;
1437-
goto valid_return;
1451+
if (target_ep == NULL) {
1452+
goto valid_return;
1453+
}
1454+
else {
1455+
goto unexpected_return;
1456+
}
14381457
}
14391458
break;
14401459
case ISEQ_TYPE_EVAL:
@@ -1448,7 +1467,12 @@ vm_throw_start(const rb_execution_context_t *ec, rb_control_frame_t *const reg_c
14481467
}
14491468

14501469
if (escape_cfp->ep == target_lep && escape_cfp->iseq->body->type == ISEQ_TYPE_METHOD) {
1451-
goto valid_return;
1470+
if (target_ep == NULL) {
1471+
goto valid_return;
1472+
}
1473+
else {
1474+
goto unexpected_return;
1475+
}
14521476
}
14531477

14541478
escape_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(escape_cfp);

0 commit comments

Comments
 (0)