Skip to content

Commit 8292873

Browse files
committed
ZJIT: Check for VM stack overflow
Previously, the included test crashed or turned into an infinite loop due to the missing check.
1 parent f7f0f67 commit 8292873

4 files changed

Lines changed: 38 additions & 2 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,6 +2584,26 @@ def test(x)
25842584
}, insns: [:opt_case_dispatch]
25852585
end
25862586

2587+
def test_stack_overflow
2588+
assert_compiles 'nil', %q{
2589+
def recurse(n)
2590+
return if n == 0
2591+
recurse(n-1)
2592+
nil # no tail call
2593+
end
2594+
2595+
recurse(2)
2596+
recurse(2)
2597+
begin
2598+
recurse(20_000)
2599+
rescue SystemStackError
2600+
# Not asserting an exception is raised here since main
2601+
# thread stack size is environment-sensitive. Only
2602+
# that we don't crash or infinite loop.
2603+
end
2604+
}, call_threshold: 2
2605+
end
2606+
25872607
def test_invokeblock
25882608
assert_compiles '42', %q{
25892609
def test

zjit/src/codegen.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType,
2020
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
2121
use crate::hir_type::{types, Type};
2222
use crate::options::get_option;
23+
use crate::cast::IntoUsize;
2324

2425
/// Ephemeral code generation state
2526
struct JITState {
@@ -1042,6 +1043,19 @@ fn gen_send_without_block_direct(
10421043
args: Vec<Opnd>,
10431044
state: &FrameState,
10441045
) -> lir::Opnd {
1046+
let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.as_usize();
1047+
// Stack overflow check: fails if CFP<=SP at any point in the callee.
1048+
asm_comment!(asm, "stack overflow check");
1049+
let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.as_usize();
1050+
// vm_push_frame() checks it against a decremented cfp, and CHECK_VM_STACK_OVERFLOW0
1051+
// adds to the margin another control frame with `&bounds[1]`.
1052+
const { assert!(RUBY_SIZEOF_CONTROL_FRAME % SIZEOF_VALUE == 0, "sizeof(rb_control_frame_t) is a multiple of sizeof(VALUE)"); }
1053+
let cfp_growth = 2 * (RUBY_SIZEOF_CONTROL_FRAME / SIZEOF_VALUE);
1054+
let peak_offset = SIZEOF_VALUE * (stack_growth + cfp_growth);
1055+
let stack_limit = asm.add(SP, peak_offset.into());
1056+
asm.cmp(CFP, stack_limit);
1057+
asm.jbe(side_exit(jit, state, StackOverflow));
1058+
10451059
// Save cfp->pc and cfp->sp for the caller frame
10461060
gen_prepare_call_with_gc(asm, state);
10471061
gen_save_sp(asm, state.stack().len() - args.len() - 1); // -1 for receiver
@@ -1059,8 +1073,7 @@ fn gen_send_without_block_direct(
10591073
});
10601074

10611075
asm_comment!(asm, "switch to new SP register");
1062-
let local_size = unsafe { get_iseq_body_local_table_size(iseq) } as usize;
1063-
let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE as usize) * SIZEOF_VALUE;
1076+
let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE;
10641077
let new_sp = asm.add(SP, sp_offset.into());
10651078
asm.mov(SP, new_sp);
10661079

zjit/src/hir.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ pub enum SideExitReason {
464464
Interrupt,
465465
BlockParamProxyModified,
466466
BlockParamProxyNotIseqOrIfunc,
467+
StackOverflow,
467468
}
468469

469470
impl std::fmt::Display for SideExitReason {

zjit/src/stats.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ make_counters! {
102102
exit_callee_side_exit,
103103
exit_obj_to_string_fallback,
104104
exit_interrupt,
105+
exit_stackoverflow,
105106
exit_optional_arguments,
106107
exit_block_param_proxy_modified,
107108
exit_block_param_proxy_not_iseq_or_ifunc,
@@ -232,6 +233,7 @@ pub fn exit_counter_ptr(reason: crate::hir::SideExitReason) -> *mut u64 {
232233
CalleeSideExit => exit_callee_side_exit,
233234
ObjToStringFallback => exit_obj_to_string_fallback,
234235
Interrupt => exit_interrupt,
236+
StackOverflow => exit_stackoverflow,
235237
BlockParamProxyModified => exit_block_param_proxy_modified,
236238
BlockParamProxyNotIseqOrIfunc => exit_block_param_proxy_not_iseq_or_ifunc,
237239
};

0 commit comments

Comments
 (0)