Bug #13930
closedException is caught in rescue above ensure
Description
Given the following code:
def foo
i = 0
3.times do |n|
begin
puts "a"
i += 1
next if i > 1
puts "b"
rescue => e
puts "rescue in foo, caught #{e}"
ensure
puts "ensure in foo, yield from foo: #{n}"
yield n
end
end
end
begin
x = 0
foo do |o|
puts o
x += 1
raise "test" if x > 1
puts "done yielding"
end
rescue => e
puts "rescue outside, caught #{e}"
ensure
puts "ensure outside"
end
The output is:
a
b
ensure in foo, yield from foo: 0
0
done yielding
a
ensure in foo, yield from foo: 1
1
rescue in foo, caught test
ensure in foo, yield from foo: 1
1
rescue outside, caught test
ensure outside
So the exception raised within the yielded block is caught in the rescue block above the ensure block which yielded. That sounds wrong to me. Or is it intended? The issue seems to be with the usage of next. Also, exception is caught inside AND outside as it seems that the ensure block ends up being called twice ?!
If I change the code to this:
def foo
i = 0
3.times do |n|
begin
puts "a"
i += 1
# next if i > 1
puts "b"
rescue => e
puts "rescue in foo, caught #{e}"
ensure
puts "ensure in foo, yield from foo: #{n}"
yield n
end
end
end
begin
x = 0
foo do |o|
puts o
x += 1
raise "test" if x > 1
puts "done yielding"
end
rescue => e
puts "rescue outside, caught #{e}"
ensure
puts "ensure outside"
end
Then the output is:
a
b
ensure in foo, yield from foo: 0
0
done yielding
a
b
ensure in foo, yield from foo: 1
1
rescue outside, caught test
ensure outside
I would have expected this output also when using next as above.
Files
Updated by shyouhei (Shyouhei Urabe) over 7 years ago
- Description updated (diff)
Updated by wanabe (_ wanabe) over 6 years ago
- File bug13930.disasm bug13930.disasm added
- File bug13930.patch bug13930.patch added
I think it's due to compile_next()
and add_ensure_iseq()
.
The following is a reduced script result.
$ ruby -e 'iseq = RubyVM::InstructionSequence.compile("1.times do; begin; p :loop; next; rescue; p :NG_rescue; ensure; raise \"test\"; end; end rescue p $!"); File.write("bug13930.disasm", iseq.disasm); iseq.eval'
:loop
:NG_rescue
#<RuntimeError: test>
Attached "bug13930.disasm" is the disasm result.
"catch type: rescue st: 0001 ed: 0019" may be wrong and it might be a good to be "ed: 0006" or "ed: 0008".
Because "0009 putself" is the receiver of "0012 opt_send_without_block <callinfo!mid:raise ...>" in ensure clause.
I wrote "bug13930.patch" but this is incorrect because:
- The patch has a performance issue because of
throw
instruction.- I haven't measured benchmark yet and I don't know how.
- "next in while loop" pattern should be also fixed.
-
compile_break()
/compile_redo()
/compile_return()
will have same issue because they useadd_ensure_iseq()
.- But I can't fix them because of "break from proc-closure" error.
Updated by jeremyevans0 (Jeremy Evans) almost 5 years ago
- Related to Bug #16618: Ensure called twice when raise in ensure added
Updated by jeremyevans0 (Jeremy Evans) almost 4 years ago
- Status changed from Open to Assigned
- Assignee set to ko1 (Koichi Sasada)
Thanks to @wanabe's insight that this issue is related to the scope of the rescue catch entry, I came up with a pull request that avoids the issues @wanabe (_ wanabe) identified in his patch: https://github.com/ruby/ruby/pull/4291. Instead of trying to fix things cleanly (which I don't know how to do), just adjust the scope of the rescue catch entry so it doesn't cover the ensure block. This approach fixes the original example and @wanabe's simplified example in this issue, as well as all failing examples in #16618. The heuristic this approach uses may not fix all issues, and it may cause issues, but with my limited knowledge of the compiler I cannot be certain of either. However, the pull request does pass all existing tests.
Updated by ko1 (Koichi Sasada) almost 4 years ago
- Status changed from Assigned to Closed
Applied in changeset git|609de71f043e8ba34f22b9993e444e2e5bb05709.
fix raise in exception with jump
add_ensure_iseq() adds ensure block to the end of
jump such as next/redo/return. However, if the rescue
cause are in the body, this rescue catches the exception
in ensure clause.
iter do
next
rescue
R
ensure
raise
end
In this case, R should not be executed, but executed without this patch.
Fixes [Bug #13930]
Fixes [Bug #16618]
A part of tests are written by @jeremyevans https://github.com/ruby/ruby/pull/4291