diff options
author | Rian McGuire <[email protected]> | 2025-04-28 22:50:29 +1000 |
---|---|---|
committer | <[email protected]> | 2025-04-28 21:50:29 +0900 |
commit | 80a1a1bb8ae8435b916ae4f66a483e91ad31356a () | |
tree | 10f0477439a3f779be6cd50656136fbc1f165402 | |
parent | 37db51b4412ef385d9c57a2b106d9014e69abeab (diff) |
YJIT: Fix potential infinite loop when OOM (GH-13186)
Avoid generating an infinite loop in the case where: 1. Block `first` is adjacent to block `second`, and the branch from `first` to `second` is a fallthrough, and 2. Block `second` immediately exits to the interpreter, and 3. Block `second` is invalidated and YJIT is OOM While pondering how to fix this, I think I've stumbled on another related edge case: 1. Block `incoming_one` and `incoming_two` both branch to block `second`. Block `incoming_one` has a fallthrough 2. Block `second` immediately exits to the interpreter (so it starts with its exit) 3. When Block `second` is invalidated, the incoming fallthrough branch from `incoming_one` might be rewritten first, which overwrites the start of block `second` with a jump to a new branch stub. 4. YJIT runs of out memory 5. The incoming branch from `incoming_two` is then rewritten, but because we're OOM we can't generate a new stub, so we use `second`'s exit as the branch target. However `second`'s exit was already overwritten with a jump to the branch stub for `incoming_one`, so `incoming_two` will end up jumping to `incoming_one`'s branch stub. Fixes [Bug #21257]
Notes: Merged: https://.com/ruby/ruby/pull/13186 Merged-By: XrXr
-rw-r--r-- | bootstraptest/test_yjit.rb | 68 | ||||
-rw-r--r-- | yjit/src/core.rs | 39 |
2 files changed, 102 insertions, 5 deletions
@@ -3667,6 +3667,74 @@ assert_equal 'new', %q{ test } assert_equal 'ok', %q{ # Try to compile new method while OOM def foo @@ -4158,7 +4158,23 @@ pub fn invalidate_block_version(blockref: &BlockRef) { } // For each incoming branch - for branchref in block.incoming.0.take().iter() { let branch = unsafe { branchref.as_ref() }; let target_idx = if branch.get_target_address(0) == Some(block_start) { 0 @@ -4198,10 +4214,18 @@ pub fn invalidate_block_version(blockref: &BlockRef) { let target_next = block.start_addr == branch.end_addr.get(); if target_next { - // The new block will no longer be adjacent. - // Note that we could be enlarging the branch and writing into the - // start of the block being invalidated. - branch.gen_fn.set_shape(BranchShape::Default); } // Rewrite the branch with the new jump target address @@ -4211,6 +4235,11 @@ pub fn invalidate_block_version(blockref: &BlockRef) { if target_next && branch.end_addr > block.end_addr { panic!("yjit invalidate rewrote branch past end of invalidated block: {:?} (code_size: {})", branch, block.code_size()); } if !target_next && branch.code_size() > old_branch_size { panic!( "invalidated branch grew in size (start_addr: {:?}, old_size: {}, new_size: {})", |