Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -2124,7 +2124,14 @@ vm_op_getidx0(mrb_state *mrb, uint32_t a, uint16_t b, mrb_sym *midp)
}
else if (tt == MRB_TT_HASH) {
if (mrb_obj_ptr(recv)->c != mrb->hash_class) goto getidx0_fallback;
regs[a] = mrb_hash_get(mrb, recv, mrb_fixnum_value(0));
{
/* same as the Hash branch of vm_op_getidx(): mrb_hash_get() can run a
default proc and move the stack, so take the result first and store
it through the refreshed `regs` */
mrb_value val = mrb_hash_get(mrb, recv, mrb_fixnum_value(0));
ci = mrb->c->ci;
regs[a] = val;
}
return VM_NEXT;
}
getidx0_fallback:
Expand Down
15 changes: 15 additions & 0 deletions test/t/hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -996,3 +996,18 @@ def h.default(k); self[k] = 1; end
y = 2
assert_equal({x:1, y:2}, {x:, y:})
end

assert('Hash#[] with a default proc that grows the VM stack') do
# The default proc re-enters the VM, and enough frames of it reallocate the
# stack. `OP_GETIDX0` and `OP_GETIDX` answer from C, so each has to store its
# result through the refreshed registers rather than the ones it came in
# with. The corruption is silent without a sanitizer, so this only fails
# reliably under `build_config/clang-asan.rb`.
def self.deep(n)
return 0 if n == 0
deep(n - 1)
end
h = Hash.new { |_, _| deep(50); :from_proc }
assert_equal :from_proc, h[0]
assert_equal :from_proc, h[1]
end
Loading