YJIT: Add frozen guard for struct aset (#15835)

We used to just skip this check (oops), but we should not allow
modifying frozen objects.
This commit is contained in:
Max Bernstein 2026-01-09 14:25:48 -05:00 committed by GitHub
parent 51ab7b0405
commit e08f316f28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2026-01-09 19:26:17 +00:00
Merged-By: tekknolagi <donotemailthisaddress@bernsteinbear.com>
3 changed files with 32 additions and 0 deletions

View File

@ -657,6 +657,26 @@ class TestYJIT < Test::Unit::TestCase
RUBY
end
def test_struct_aset_guards_recv_is_not_frozen
assert_compiles(<<~RUBY, result: :ok, exits: { opt_send_without_block: 1 })
def foo(obj)
obj.foo = 123
end
Foo = Struct.new(:foo)
obj = Foo.new(123)
100.times do
foo(obj)
end
obj.freeze
begin
foo(obj)
rescue FrozenError
:ok
end
RUBY
end
def test_getblockparam
assert_compiles(<<~'RUBY', insns: [:getblockparam])
def foo &blk

View File

@ -8973,6 +8973,17 @@ fn gen_struct_aset(
assert!(unsafe { RB_TYPE_P(comptime_recv, RUBY_T_STRUCT) });
assert!((off as i64) < unsafe { RSTRUCT_LEN(comptime_recv) });
// Even if the comptime recv was not frozen, future recv may be. So we need to emit a guard
// that the recv is not frozen.
// We know all structs are heap objects, so we can check the flag directly.
let recv = asm.stack_opnd(1);
let recv = asm.load(recv);
let flags = asm.load(Opnd::mem(VALUE_BITS, recv, RUBY_OFFSET_RBASIC_FLAGS));
asm.test(flags, (RUBY_FL_FREEZE as u64).into());
asm.jnz(Target::side_exit(Counter::opt_aset_frozen));
// Not frozen, so we can proceed.
asm_comment!(asm, "struct aset");
let val = asm.stack_pop(1);

View File

@ -489,6 +489,7 @@ make_counters! {
opt_aset_not_array,
opt_aset_not_fixnum,
opt_aset_not_hash,
opt_aset_frozen,
opt_case_dispatch_megamorphic,