From b00545b9652782b5ab07d16dc7729b0c69242d3c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 11 May 2026 13:45:19 -0700 Subject: merge revision(s) f89b07ef0046257dd796a2e615cc063072114f16: [Backport #21940] [PATCH] Mark `$_` as box-dynamic to bypass Box gvar_tbl cache `$_` is updated through svar (rb_lastline_set), bypassing rb_gvar_set, so the Box gvar_tbl cache is never invalidated and returns a stale value. Call rb_gvar_box_dynamic so gvar_use_box_tbl() skips the cache. Fixes [Bug #21940](https://bugs.ruby-lang.org/issues/21940) --- io.c | 1 + test/ruby/test_box.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/io.c b/io.c index 7088f036c5..3d2e340b03 100644 --- a/io.c +++ b/io.c @@ -15738,6 +15738,7 @@ Init_IO(void) rb_define_virtual_variable("$_", get_LAST_READ_LINE, set_LAST_READ_LINE); rb_gvar_ractor_local("$_"); + rb_gvar_box_dynamic("$_"); rb_define_method(rb_cIO, "initialize_copy", rb_io_init_copy, 1); rb_define_method(rb_cIO, "reopen", rb_io_reopen, -1); diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 1e162279d4..313a013c07 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -551,6 +551,49 @@ class TestBox < Test::Unit::TestCase end; end + def test_lastline_not_cached_in_box + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + r, w = IO.pipe + w.write("first\nsecond\n") + w.close + STDIN.reopen(r) + via_gets = Ruby::Box.new.eval(<<~'CODE') + gets + _ = $_ + gets + $_ + CODE + assert_equal "second\n", via_gets + end; + end + + def test_lastline_not_cached_in_nested_boxes + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + r, w = IO.pipe + w.write("outer1\ninner1\ninner2\nouter2\n") + w.close + STDIN.reopen(r) + inner_via_gets, outer_via_gets = Ruby::Box.new.eval(<<~'CODE') + gets + _ = $_ + + inner_result = Ruby::Box.new.eval(<<~'INNER') + gets + _ = $_ + gets + $_ + INNER + + gets + [inner_result, $_] + CODE + assert_equal "inner2\n", inner_via_gets + assert_equal "outer2\n", outer_via_gets + end; + end + def test_load_path_and_loaded_features setup_box -- cgit v1.2.3