Project

General

Profile

Actions

Bug #16618

closed

Ensure called twice when raise in ensure

Added by davidsiaw (David Siaw) about 1 year ago. Updated 17 days ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
ruby -v:
ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux-musl]
[ruby-core:97104]

Description

Under very specific circumstances (a return with code after it), a raise in an ensure block causes the ensure block to be called twice after rescuing in a rescue block above it.

Here is a very small reproduction case:

class ABC < StandardError; end

def meow
  return
  puts 'a'
rescue ABC
  puts 'rescue'
ensure
  puts 'ensure'
  raise ABC
end

meow

Causes the output

ensure
rescue
ensure
Traceback (most recent call last):
    1: from spec/meow.rb:13:in `<main>'
spec/meow.rb:10:in `meow': ABC (ABC)

However, if I remove the code below the return, the behavior is more reasonable:

class ABC < StandardError; end

def meow
  return
rescue ABC
  puts 'rescue'
ensure
  puts 'ensure'
  raise ABC
end

meow
ensure
Traceback (most recent call last):
    1: from spec/meow.rb:14:in `<main>'
spec/meow.rb:9:in `meow': ABC (ABC)

It does not seem to matter what code comes after return. It also does not seem to matter what code comes before the return, as long as the return is run. It could throw errors or it might not, but there seems to be a definite requirement that there is code after the return for this bug to occur.

Possibly related to #13930? I am not sure.


Related issues

Related to Ruby master - Bug #13930: Exception is caught in rescue above ensureClosedko1 (Koichi Sasada)Actions
Actions #1

Updated by davidsiaw (David Siaw) about 1 year ago

  • Subject changed from Rescue called twice when raise in ensure to Ensure called twice when raise in ensure
Actions #2

Updated by jeremyevans0 (Jeremy Evans) about 1 year ago

  • Related to Bug #13930: Exception is caught in rescue above ensure added
Actions #3

Updated by davidsiaw (David Siaw) about 1 year ago

I found another issue with this where

def meow
  puts 'start'
  begin
    return
    puts 'should not run'
  rescue
    puts 'inner rescue'
  ensure
    puts 'inner ensure'
  end
rescue
  puts 'rescue'
ensure
  puts 'ensure'
  raise 'heh'
end

meow

gives the output

start
inner ensure
ensure
inner rescue
inner ensure
ensure
Traceback (most recent call last):
    1: from meow.rb:19:in `<main>'
meow.rb:16:in `meow': heh (RuntimeError)

It turns out that the raise gets rescued by the inner rescue, not the rescue above it even though that code should have ended already.

Actions #4

Updated by ko1 (Koichi Sasada) 17 days ago

  • Status changed from Open 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

Actions

Also available in: Atom PDF