Follow the ensure codegen of mruby-compiler, and fix a pending return past the frame - #313
Merged
HirohitoHigashi merged 3 commits intoSep 13, 2026
Conversation
Since mruby b7df80fc4 the compiler emits OP_RESCUE at the entry of an ensure to decide whether `$!` should name the exception. R[a] is then nil on a normal entry, or a break/return passing through, neither an exception, and the assertion in op_rescue aborted every `begin ... ensure ... end` compiled by that compiler. Check the type before asking mrbc_obj_is_kind_of, which cannot classify those values. Bytecode from an older compiler is unaffected: it emits OP_RESCUE only in a rescue clause, where R[a] is always an exception.
sub_op_return parked the value of a `return` that has an ensure clause
to run in regs[nregs], the register just past the frame. A method the
ensure clause calls builds its frame exactly there, so anything but a C
function in the clause clobbered the value, and the caller then read
garbage:
def m
return 42
ensure
puts "bye" # Kernel#puts is Ruby: the frame overlaps regs[nregs]
end
Keep the value in a heap box that the MRBC_TT_RETURN marker points to,
as OP_JMPUW keeps its target in `handle`. OP_RAISEIF takes it back and
returns through sub_op_return, which also covers the next ensure on the
way out, the initialize/super rules and the top level; the old path
asserted in mrbc_pop_callinfo on a top-level return.
A raise or break in the ensure clause abandons the return and leaves
the marker in a register, so mrbc_pop_callinfo and mrbc_vm_end release
the box with the register.
The new test also covers `$!` in an ensure clause, which the previous
commit made reachable.
Since mruby b2d2cd15e2463bcc4941b5a293802f3a9a83ae96 the compiler
sends __pat_values to whatever #deconstruct_keys answered, even for a
pattern that names no keys, and relies on the receiver's class to make
that the type check: Hash answers the values, and Object raises the
TypeError CRuby raises for a hook that answers anything else.
mruby/c only had Hash#__pat_values, so a hook answering nil or a
Symbol ended in a NoMethodError that leaked the helper's name:
undefined local variable or method '__pat_values' for NilClass
Add the Object side with the same message as mruby. The test that
expected a nil answer to read as a failed match now expects the
TypeError, which is what CRuby and mruby raise, and a non-Hash answer
is covered too.
Member
|
I generally agree with this proposal. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains three patches.
Two of them catch up with a change in upstream mruby-compiler; the other fixes a long-standing bug in mruby/c itself.
They are in one PR because neither passes CI on its own.
Let OP_RESCUE answer false for a non-exception value
Follows mruby/mruby@b7df80f ("mruby-compiler: name the exception as
$!in a rescue modifier and an ensure"), which picoruby picked up through its mruby-compiler sync.The codegen now emits OP_RESCUE at the entry of an ensure clause to decide whether
$!should name the exception it is unwinding:So
R[a]of OP_RESCUE is no longer always an exception: it is nil when the clause is entered normally, or a break/return passing through.The mruby VM was changed accordingly (
!mrb_break_p(exc) && ...), while op_rescue in mruby/c assertedMRBC_TT_EXCEPTIONand aborted everybegin ... ensure ... endon the normal path:This is backward compatible.
An mrbc from before that change (at mruby 4.0) emits OP_RESCUE only inside a rescue clause, where
R[a]is always an exception, so the new check is always true there and the result is unchanged.Carry a pending return's value in a box, not past the frame
This is an existing bug, independent of the compiler: bytecode from an mrbc (even in mruby 4.0) crashes the same way.
When a
returnhas an ensure clause to run, sub_op_return parked the return value inregs[nregs], the register just past the current frame, and jumped to the clause.But a method called from the ensure clause builds its frame exactly there, so anything but a C function in the clause clobbered the value:
This patch solves that by introducing
release_pending_return()(see diff)test/ensure_return_test.rbcovers a method call in the ensure clause, a local rewritten by the clause, two ensure clauses, a nested pending return, a raise replacing the return, an object value, and$!(now it is set by mruby-compiler) in an ensure clause entered normally, by an exception, and by a break.Add
Object#__pat_valuesfor a hash pattern's type checkFollows mruby/mruby@b2d2cd1 ("mruby-compiler: hold
#deconstruct_keysto the Hash it has to answer"). The compiler now sends__pat_valuesto whatever#deconstruct_keysanswered, even for a pattern with no keys, and relies on the receiver's class for the type check:Hash#__pat_valuesanswers the values,Object#__pat_valuesraisesTypeError: deconstruct_keys must return Hash.Backward compatible as well: an older mrbc never sends
__pat_valuesto anything but a Hash.