Skip to content

Commit 5fc0192

Browse files
committed
ZJIT: Guard receiver type in inline Array method handlers
inline_array_push, inline_array_pop, and inline_array_aref did not guard/coerce the receiver to Array type before emitting Array-specific HIR instructions (ArrayPush, ArrayPop, ArrayLength, ArrayAref). This caused a MismatchedOperandType validation failure when these handlers were called from the InvokeSuper CFUNC path, where the receiver (self) has type BasicObject rather than Array. The crash was triggered by representable gem's Binding::Map#<< calling super(binding) to invoke Array#<< on an Array subclass. Add likely_a + coerce_to checks matching the pattern used by inline_array_aset. This is a no-op in the normal SendWithoutBlock path where the caller already adds a GuardType, but correctly inserts the guard when called from InvokeSuper.
1 parent 20eeb72 commit 5fc0192

File tree

2 files changed

+161
-1
lines changed

2 files changed

+161
-1
lines changed

zjit/src/cruby_methods.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,10 @@ fn inline_kernel_block_given_p(fun: &mut hir::Function, block: hir::BlockId, _re
342342

343343
fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
344344
if let &[index] = args {
345-
if fun.likely_a(index, types::Fixnum, state) {
345+
if fun.likely_a(recv, types::Array, state)
346+
&& fun.likely_a(index, types::Fixnum, state)
347+
{
348+
let recv = fun.coerce_to(block, recv, types::Array, state);
346349
let index = fun.coerce_to(block, index, types::Fixnum, state);
347350
let index = fun.push_insn(block, hir::Insn::UnboxFixnum { val: index });
348351
let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv });
@@ -386,6 +389,8 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
386389
fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
387390
// Inline only the case of `<<` or `push` when called with a single argument.
388391
if let &[val] = args {
392+
if !fun.likely_a(recv, types::Array, state) { return None; }
393+
let recv = fun.coerce_to(block, recv, types::Array, state);
389394
let _ = fun.push_insn(block, hir::Insn::ArrayPush { array: recv, val, state });
390395
return Some(recv);
391396
}
@@ -395,6 +400,8 @@ fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In
395400
fn inline_array_pop(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option<hir::InsnId> {
396401
// Only inline the case of no arguments.
397402
let &[] = args else { return None; };
403+
if !fun.likely_a(recv, types::Array, state) { return None; }
404+
let recv = fun.coerce_to(block, recv, types::Array, state);
398405
fun.guard_not_shared(block, recv, state);
399406
Some(fun.push_insn(block, hir::Insn::ArrayPop { array: recv, state }))
400407
}

zjit/src/hir/opt_tests.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8539,6 +8539,159 @@ mod hir_opt_tests {
85398539
");
85408540
}
85418541

8542+
#[test]
8543+
fn test_optimize_array_push_with_array_subclass() {
8544+
eval("
8545+
class PushSubArray < Array
8546+
def <<(val) = super
8547+
end
8548+
test = PushSubArray.new
8549+
test << 1
8550+
");
8551+
assert_snapshot!(hir_string_proc("PushSubArray.new.method(:<<)"), @r"
8552+
fn <<@<compiled>:3:
8553+
bb0():
8554+
EntryPoint interpreter
8555+
v1:BasicObject = LoadSelf
8556+
v2:BasicObject = GetLocal :val, l0, SP@4
8557+
Jump bb2(v1, v2)
8558+
bb1(v5:BasicObject, v6:BasicObject):
8559+
EntryPoint JIT(0)
8560+
Jump bb2(v5, v6)
8561+
bb2(v8:BasicObject, v9:BasicObject):
8562+
PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010)
8563+
v21:CPtr = GetLEP
8564+
v22:RubyValue = LoadField v21, :_ep_method_entry@0x1038
8565+
v23:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v22, Value(VALUE(0x1040))
8566+
v24:RubyValue = LoadField v21, :_ep_specval@0x1048
8567+
v25:FalseClass = GuardBitEquals v24, Value(false)
8568+
v26:Array = GuardType v8, Array
8569+
ArrayPush v26, v9
8570+
IncrCounter inline_cfunc_optimized_send_count
8571+
CheckInterrupts
8572+
Return v26
8573+
");
8574+
}
8575+
8576+
#[test]
8577+
fn test_optimize_array_pop_with_array_subclass() {
8578+
eval("
8579+
class PopSubArray < Array
8580+
def pop = super
8581+
end
8582+
test = PopSubArray.new([1])
8583+
test.pop
8584+
");
8585+
assert_snapshot!(hir_string_proc("PopSubArray.new.method(:pop)"), @r"
8586+
fn pop@<compiled>:3:
8587+
bb0():
8588+
EntryPoint interpreter
8589+
v1:BasicObject = LoadSelf
8590+
Jump bb2(v1)
8591+
bb1(v4:BasicObject):
8592+
EntryPoint JIT(0)
8593+
Jump bb2(v4)
8594+
bb2(v6:BasicObject):
8595+
PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010)
8596+
v17:CPtr = GetLEP
8597+
v18:RubyValue = LoadField v17, :_ep_method_entry@0x1038
8598+
v19:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v18, Value(VALUE(0x1040))
8599+
v20:RubyValue = LoadField v17, :_ep_specval@0x1048
8600+
v21:FalseClass = GuardBitEquals v20, Value(false)
8601+
PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010)
8602+
v27:CPtr = GetLEP
8603+
v28:RubyValue = LoadField v27, :_ep_method_entry@0x1038
8604+
v29:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v28, Value(VALUE(0x1040))
8605+
v30:RubyValue = LoadField v27, :_ep_specval@0x1048
8606+
v31:FalseClass = GuardBitEquals v30, Value(false)
8607+
v22:Array = GuardType v6, Array
8608+
v23:CUInt64 = LoadField v22, :_rbasic_flags@0x1049
8609+
v24:CUInt64 = GuardNoBitsSet v23, RUBY_ELTS_SHARED=CUInt64(4096)
8610+
v25:BasicObject = ArrayPop v22
8611+
IncrCounter inline_cfunc_optimized_send_count
8612+
CheckInterrupts
8613+
Return v25
8614+
");
8615+
}
8616+
8617+
#[test]
8618+
fn test_optimize_array_aref_with_array_subclass_and_fixnum() {
8619+
eval("
8620+
class ArefSubArray < Array
8621+
def [](idx) = super
8622+
end
8623+
test = ArefSubArray.new([1])
8624+
test[0]
8625+
");
8626+
assert_snapshot!(hir_string_proc("ArefSubArray.new.method(:[])"), @r"
8627+
fn []@<compiled>:3:
8628+
bb0():
8629+
EntryPoint interpreter
8630+
v1:BasicObject = LoadSelf
8631+
v2:BasicObject = GetLocal :idx, l0, SP@4
8632+
Jump bb2(v1, v2)
8633+
bb1(v5:BasicObject, v6:BasicObject):
8634+
EntryPoint JIT(0)
8635+
Jump bb2(v5, v6)
8636+
bb2(v8:BasicObject, v9:BasicObject):
8637+
PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010)
8638+
v21:CPtr = GetLEP
8639+
v22:RubyValue = LoadField v21, :_ep_method_entry@0x1038
8640+
v23:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v22, Value(VALUE(0x1040))
8641+
v24:RubyValue = LoadField v21, :_ep_specval@0x1048
8642+
v25:FalseClass = GuardBitEquals v24, Value(false)
8643+
PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010)
8644+
v35:CPtr = GetLEP
8645+
v36:RubyValue = LoadField v35, :_ep_method_entry@0x1038
8646+
v37:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v36, Value(VALUE(0x1040))
8647+
v38:RubyValue = LoadField v35, :_ep_specval@0x1048
8648+
v39:FalseClass = GuardBitEquals v38, Value(false)
8649+
v26:Array = GuardType v8, Array
8650+
v27:Fixnum = GuardType v9, Fixnum
8651+
v28:CInt64 = UnboxFixnum v27
8652+
v29:CInt64 = ArrayLength v26
8653+
v30:CInt64 = GuardLess v28, v29
8654+
v31:CInt64[0] = Const CInt64(0)
8655+
v32:CInt64 = GuardGreaterEq v30, v31
8656+
v33:BasicObject = ArrayAref v26, v32
8657+
IncrCounter inline_cfunc_optimized_send_count
8658+
CheckInterrupts
8659+
Return v33
8660+
");
8661+
}
8662+
8663+
#[test]
8664+
fn test_dont_optimize_array_aref_with_array_subclass_and_non_fixnum() {
8665+
eval("
8666+
class ArefSubArrayRange < Array
8667+
def [](idx) = super
8668+
end
8669+
test = ArefSubArrayRange.new([1, 2, 3])
8670+
test[0..1]
8671+
");
8672+
assert_snapshot!(hir_string_proc("ArefSubArrayRange.new.method(:[])"), @r"
8673+
fn []@<compiled>:3:
8674+
bb0():
8675+
EntryPoint interpreter
8676+
v1:BasicObject = LoadSelf
8677+
v2:BasicObject = GetLocal :idx, l0, SP@4
8678+
Jump bb2(v1, v2)
8679+
bb1(v5:BasicObject, v6:BasicObject):
8680+
EntryPoint JIT(0)
8681+
Jump bb2(v5, v6)
8682+
bb2(v8:BasicObject, v9:BasicObject):
8683+
PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010)
8684+
v21:CPtr = GetLEP
8685+
v22:RubyValue = LoadField v21, :_ep_method_entry@0x1038
8686+
v23:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v22, Value(VALUE(0x1040))
8687+
v24:RubyValue = LoadField v21, :_ep_specval@0x1048
8688+
v25:FalseClass = GuardBitEquals v24, Value(false)
8689+
v26:BasicObject = CCallVariadic v8, :Array#[]@0x1050, v9
8690+
CheckInterrupts
8691+
Return v26
8692+
");
8693+
}
8694+
85428695
#[test]
85438696
fn test_optimize_array_length() {
85448697
eval("

0 commit comments

Comments
 (0)