Issue #22307 has been reported by hsbt (Hiroshi SHIBATA). ---------------------------------------- Bug #22307: Ruby::Box: assignments to C-backed global variables do not reach C https://bugs.ruby-lang.org/issues/22307 * Author: hsbt (Hiroshi SHIBATA) * Status: Open * ruby -v: ruby 4.1.0dev (2026-09-10T03:20:50Z master fe58143f12) +YJIT +MN +PRISM [arm64-darwin27] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- Under `RUBY_BOX=1`, assigning a global variable backed by a C variable only updates a per-box table. The setter never runs and the interpreter keeps reading the C variable. This happens in the main box too. ``` $ RUBY_BOX=1 ruby --disable-gems -e '$/ = "!"; p "hello!".chomp, [$/, $-0]; $/ = 1; $-a = 1; p $stdout.equal?(STDOUT)' "hello!" ["!", "\n"] false ``` Without `RUBY_BOX`, `chomp` returns `"hello"`, `$-0` follows `$/`, and `$/ = 1` raises `TypeError`. 4.0.6 behaves the same. `gvar_use_box_tbl` (`variable.c:1025`) sends every variable in a user box, the main box included, through `box->gvar_tbl` unless it is marked `box_dynamic` or `box_ready`. The first read clones the getter's value into the table and later writes stay there. So `$/` and `$-0` get separate slots for one `rb_rs`, `$stdout` becomes a copy of `STDOUT`, and validating setters never run. So far this has been fixed one variable at a time with those two marks, in #21940 (`$_`), #21933 (`$~`) and #21991 (`$!`, `$@`). The open #22280, #22282 and #21867 have PRs that do the same. https://github.com/ruby/ruby/pull/18711 covers the rest in the main box by skipping the table for any variable whose setter is not the plain Ruby one, so the main box behaves like Ruby without boxes. Optional boxes keep the table, and `TestBox#test_global_variables` asserts that `$-0` and `$,` set in one box stay there. That holds only for Ruby code reading the variable. C code in every box reads the one C variable. ``` $ RUBY_BOX=1 ruby --disable-gems -e 'p Ruby::Box.new.eval(%q{$/ = "!"; ["hello!".chomp, $/, $-0]})' ["hello!", "!", "\n"] ``` I would like a decision on what these variables mean in an optional box. The first option keeps the box-local copy and accepts that the interpreter ignores it, which is where #18711 stops. The second shares them across all boxes like `box_dynamic` and changes `test_global_variables`. The third makes the C side per box, so every reader of `rb_rs` and the like looks it up through the current box. -- https://bugs.ruby-lang.org/