diff options
| author | Takashi Kokubun <takashikkbn@gmail.com> | 2026-05-11 13:45:19 -0700 |
|---|---|---|
| committer | Takashi Kokubun <takashikkbn@gmail.com> | 2026-05-11 13:45:19 -0700 |
| commit | b00545b9652782b5ab07d16dc7729b0c69242d3c (patch) | |
| tree | 44719cbf4caa0ae18365a9dddf7974721db4fa45 | |
| parent | dd78605b2d06600750c331f307083d60df702814 (diff) | |
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)
| -rw-r--r-- | io.c | 1 | ||||
| -rw-r--r-- | test/ruby/test_box.rb | 43 |
2 files changed, 44 insertions, 0 deletions
@@ -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 |
