Project

General

Profile

Actions

Bug #21991

closed

`$!` stays as the first exception in Ruby Box

Bug #21991: `$!` stays as the first exception in Ruby Box

Added by tikkss (Tsutomu Katsube) 28 days ago. Updated about 14 hours ago.

Status:
Closed
Target version:
-
ruby -v:
ruby 4.0.2 (2026-03-17 revision d3da9fec82) +PRISM [x86_64-darwin24]
[ruby-core:125235]

Description

Description

When Ruby Box is enabled (RUBY_BOX=1), $! inside rescue does not change after the first exception.

$! is expected to show the exception for each rescue block, but it always shows the first one.

Steps to reproduce

# test.rb
begin
  raise "First error"
rescue
  pp $!
end

begin
  raise "Second error"
rescue
  pp $!
end

begin
  raise "Third error"
rescue
  pp $!
end

Expected result

$ RUBY_BOX=1 ruby test.rb
ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/4.0/Ruby/Box.html for known issues, etc.
#<RuntimeError: First error>
#<RuntimeError: Second error>
#<RuntimeError: Third error>

Actual result

$ RUBY_BOX=1 ruby test.rb
ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/4.0/Ruby/Box.html for known issues, etc.
#<RuntimeError: First error>
#<RuntimeError: First error>
#<RuntimeError: First error>

Additional information

This issue does not reproduce when RUBY_BOX=1 is not set:

$ ruby test.rb
#<RuntimeError: First error>
#<RuntimeError: Second error>
#<RuntimeError: Third error>

Also, this issue does not reproduce when the exception object is captured explicitly in the rescue clause:

# test.rb
begin
  raise "First error"
rescue => e
  pp e
end

begin
  raise "Second error"
rescue => e
  pp e
end

begin
  raise "Third error"
rescue => e
  pp e
end
$ RUBY_BOX=1 ruby test.rb
ruby: warning: Ruby::Box is experimental, and the behavior may change in the future!
See https://docs.ruby-lang.org/en/4.0/Ruby/Box.html for known issues, etc.
#<RuntimeError: First error>
#<RuntimeError: Second error>
#<RuntimeError: Third error>

Related issues 1 (1 open0 closed)

Related to Ruby - Bug #21823: $! is nil inside rescue block when $! is accessed before raise in Ruby::BoxOpentagomoris (Satoshi Tagomori)Actions

Updated by byroot (Jean Boussier) 27 days ago Actions #1

  • Related to Bug #21823: $! is nil inside rescue block when $! is accessed before raise in Ruby::Box added

Updated by byroot (Jean Boussier) 27 days ago Actions #2

  • Assignee set to tagomoris (Satoshi Tagomori)

Updated by dak2 (Daichi Kamiyama) 3 days ago Actions #3 [ruby-core:125414]

I created a patch for this issue.

https://github.com/ruby/ruby/pull/16863

@tagomoris (Satoshi Tagomori) Could you review this?

Updated by dak2 (Daichi Kamiyama) about 14 hours ago Actions #4

  • Status changed from Open to Closed

Applied in changeset git|d4727cd4e63c7bc1bc95a96b7f3c6de568bc5b6e.


Ruby::Box fix stale cached values for exception-related global variables ($! and $@)

Ruby::Box fix stale cached values for exception-related global variables ($! and $@)

The exception-related virtual variables $! (current exception) and
$@ (its backtrace) are stored on the execution context (ec->errinfo
and the rescue/ensure frame's local slot accessed via errinfo_place),
not in box->gvar_tbl. Caching their values in box->gvar_tbl makes the
second read return a stale value from the previous raise/rescue:

begin; raise "first";  rescue; p $!; end
begin; raise "second"; rescue; p $!; end
# before: #<RuntimeError: first> / #<RuntimeError: first>
# after:  #<RuntimeError: first> / #<RuntimeError: second>
begin; raise "first";  rescue; p $@.first; end
begin; raise "second"; rescue; p $@.first; end
# before: same backtrace returned for both
# after:  distinct backtrace per raise

Fixes Bug #21991
Related PR: https://github.com/ruby/ruby/pull/16303

Actions

Also available in: PDF Atom