use crate::instruction::OpCode;
use super::super::super::HighLevelEmitter;
impl HighLevelEmitter {
pub(super) fn detect_else(
&self,
branch_offset: usize,
false_offset: usize,
) -> Option<(usize, usize)> {
let target_index = *self.index_by_offset.get(&false_offset)?;
if target_index == 0 {
return None;
}
let jump = self.program.get(target_index.checked_sub(1)?)?;
if !matches!(jump.opcode, OpCode::Jmp | OpCode::Jmp_L) {
return None;
}
let target = self.forward_jump_target(jump)?;
if target <= false_offset {
return None;
}
let jump_offset = jump.offset;
let has_external_entry = self.program.iter().any(|ins| {
Self::is_conditional_branch(ins.opcode)
&& ins.offset < branch_offset
&& self
.forward_jump_target(ins)
.map(|target| target > branch_offset && target <= jump_offset)
.unwrap_or(false)
});
if has_external_entry {
return None;
}
Some((jump_offset, target))
}
pub(super) fn detect_implicit_else(
&self,
branch_offset: usize,
false_offset: usize,
) -> Option<usize> {
let target_index = *self.index_by_offset.get(&false_offset)?;
if target_index == 0 {
return None;
}
let prev = self.program.get(target_index.checked_sub(1)?)?;
let is_direct_terminator = matches!(
prev.opcode,
OpCode::Abort | OpCode::Abortmsg | OpCode::Throw | OpCode::Ret
);
let is_noreturn_call = matches!(prev.opcode, OpCode::Call | OpCode::Call_L)
&& self
.call_targets_by_offset
.get(&prev.offset)
.is_some_and(|target| self.noreturn_method_offsets.contains(target));
if !is_direct_terminator && !is_noreturn_call {
return None;
}
if self.catch_targets.contains_key(&false_offset)
|| self.finally_targets.contains_key(&false_offset)
{
return None;
}
if self.has_inner_escape_to_false_target(branch_offset, false_offset) {
return None;
}
if self
.pending_closers
.get(&false_offset)
.copied()
.unwrap_or(0)
> 1
{
return None;
}
let else_end = self.detect_implicit_else_end(false_offset);
if !self.implicit_else_has_body(target_index, else_end) {
return None;
}
if self.finally_body_has_sequential_cleanup(false_offset, target_index, else_end) {
return None;
}
if self
.pending_closers
.keys()
.any(|&offset| offset > false_offset && offset < else_end)
{
return None;
}
Some(else_end)
}
fn has_inner_escape_to_false_target(&self, branch_offset: usize, false_offset: usize) -> bool {
self.program.iter().any(|ins| {
ins.offset > branch_offset
&& ins.offset < false_offset
&& Self::is_conditional_branch(ins.opcode)
&& self.forward_jump_target(ins) == Some(false_offset)
})
}
fn detect_implicit_else_end(&self, false_offset: usize) -> usize {
let next_boundary = self
.catch_targets
.keys()
.chain(self.finally_targets.keys())
.filter(|&&offset| offset > false_offset)
.min()
.copied();
let fallback = self.program.last().map(|ins| ins.offset + 1);
next_boundary.unwrap_or_else(|| fallback.unwrap_or(false_offset + 1))
}
fn implicit_else_has_body(&self, target_index: usize, else_end: usize) -> bool {
let end_index = self
.index_by_offset
.range(else_end..)
.next()
.map(|(_, idx)| *idx)
.unwrap_or(self.program.len());
self.program[target_index..end_index].iter().any(|ins| {
!matches!(
ins.opcode,
OpCode::Endtry | OpCode::EndtryL | OpCode::Nop | OpCode::Jmp | OpCode::Jmp_L
)
})
}
fn finally_body_has_sequential_cleanup(
&self,
false_offset: usize,
target_index: usize,
else_end: usize,
) -> bool {
let inside_finally = self
.finally_body_ranges
.iter()
.any(|&(start, end)| false_offset >= start && false_offset < end);
if !inside_finally {
return false;
}
let end_index = self
.index_by_offset
.range(else_end..)
.next()
.map(|(_, idx)| *idx)
.unwrap_or(self.program.len());
self.program[target_index..end_index]
.iter()
.any(|ins| matches!(ins.opcode, OpCode::Endfinally))
}
}