Skip to content

Fix Binding#local_variable_get inside a block in a for loop - #9850

Open
sferik wants to merge 1 commit into
jruby:masterfrom
sferik:binding-for-loop-local
Open

sferik wants to merge 1 commit into
jruby:masterfrom
sferik:binding-for-loop-local

Conversation

@sferik

@sferik sferik commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #9849.

Binding#local_variable_get and #local_variable_set looked up a variable's depth in the static scopes and then used that depth to index the dynamic scopes. Inside a for loop those don't line up. The interpreter gives the loop body a dynamic scope of its own, but a block inside the loop has the enclosing method's static scope as its parent, skipping the loop. So from a block in a for loop the depth landed one scope short, on the loop body's empty DynamicScope0, which raised "DynamicScope0 only supports scopes with 0 variables". eval wasn't affected because it gets its depths from the IR scopes, which already account for for loops.

This walks the dynamic scopes instead and uses the first one whose own static scope has the variable, stopping at the method's scope as isDefinedOrImplicit does. That's right whether or not the loop body has a scope of its own, which differs between the interpreter and the JIT. New variables still go into the binding's eval scope, and implicit it is still rejected the same way.

I added a spec for each method to ruby/spec's local_variable_get_spec.rb and local_variable_set_spec.rb. Both fail on master and pass with this change. I also compared about a dozen binding cases against CRuby in the interpreter and both JIT modes, including nested for loops, variables declared in the loop body, Proc#binding, and new variables set through the binding, and they all match. The Binding, eval, Proc#binding and for specs and the MRI eval, proc, iterator and syntax tests pass in all three modes.

Binding#local_variable_get and #local_variable_set took a variable's
depth from the static scopes and used it to index the dynamic ones.
The two chains don't line up inside a for loop: the loop body gets its
own dynamic scope in the interpreter, but a block inside it has the
enclosing method's static scope as its parent. So from a block in a
for loop, the depth landed one scope short, on the loop body's empty
scope, and raised "DynamicScope0 only supports scopes with 0
variables".

Walk the dynamic scopes instead and use the first one whose static
scope has the variable, stopping at the method's scope as before.

Fixes jruby#9849
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Binding#local_variable_get fails inside a block in a for loop

1 participant