Skip to content

Commit eb7acd7

Browse files
authored
ZJIT: Nil-fill locals in direct send (ruby#15536)
Avoid garbage reads from locals in eval. Before the fix the test fails with <"[\"x\", \"x\", \"x\", \"x\"]"> expected but was <"[\"x\", \"x\", \"x\", \"x286326928\"]">.
1 parent 8f81d2b commit eb7acd7

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,21 @@ def test = foo(1)
355355
}, call_threshold: 2
356356
end
357357

358+
def test_nonparam_local_nil_in_jit_call
359+
# Non-parameter locals must be initialized to nil in JIT-to-JIT calls.
360+
# Use dead code (if false) to create locals without initialization instructions.
361+
# Then eval a string that accesses the uninitialized locals.
362+
assert_compiles '["x", "x", "x", "x"]', %q{
363+
def f(a)
364+
a ||= 1
365+
if false; b = 1; end
366+
eval("-> { p 'x#{b}' }")
367+
end
368+
369+
4.times.map { f(1).call }
370+
}, call_threshold: 2
371+
end
372+
358373
def test_setlocal_on_eval
359374
assert_compiles '1', %q{
360375
@b = binding

zjit/src/codegen.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,16 @@ fn gen_send_without_block_direct(
13911391
0
13921392
};
13931393

1394+
// Fill non-parameter locals with nil (they may be read by eval before being written)
1395+
let num_params = params.size.to_usize();
1396+
if local_size > num_params {
1397+
asm_comment!(asm, "initialize non-parameter locals to nil");
1398+
for local_idx in num_params..local_size {
1399+
let offset = local_size_and_idx_to_bp_offset(local_size, local_idx);
1400+
asm.store(Opnd::mem(64, SP, -offset * SIZEOF_VALUE_I32), Qnil.into());
1401+
}
1402+
}
1403+
13941404
// Make a method call. The target address will be rewritten once compiled.
13951405
let iseq_call = IseqCall::new(iseq, num_optionals_passed);
13961406
let dummy_ptr = cb.get_write_ptr().raw_ptr(cb);
@@ -2369,8 +2379,8 @@ c_callable! {
23692379
let pc = unsafe { rb_iseq_pc_at_idx(iseq, entry_insn_idxs[iseq_call.jit_entry_idx.to_usize()]) };
23702380
unsafe { rb_set_cfp_pc(cfp, pc) };
23712381

2372-
// JIT-to-JIT calls don't set SP or fill nils to uninitialized (non-argument) locals.
2373-
// We need to set them if we side-exit from function_stub_hit.
2382+
// Successful JIT-to-JIT calls fill nils to non-parameter locals in generated code.
2383+
// If we side-exit from function_stub_hit (before JIT code runs), we need to set them here.
23742384
fn prepare_for_exit(iseq: IseqPtr, cfp: CfpPtr, sp: *mut VALUE, compile_error: &CompileError) {
23752385
unsafe {
23762386
// Set SP which gen_push_frame() doesn't set

0 commit comments

Comments
 (0)