use crate::ir::expr::{ConstVal, Expr};
use crate::ir::stmt::Stmt;
pub fn dead_end_infinite_while(s: &Stmt) -> bool {
let (label, body, cond) = match s {
Stmt::While { cond, body } => (None, body.as_ref(), cond),
Stmt::Labeled { label, body } => match &**body {
Stmt::While { cond, body } => (Some(label.as_str()), body.as_ref(), cond),
_ => return false,
},
_ => return false,
};
if !matches!(cond, Expr::Const(ConstVal::Int(1))) {
return false;
}
!has_break_exiting(body, label, 0)
}
fn has_break_exiting(s: &Stmt, lbl: Option<&str>, depth: usize) -> bool {
match s {
Stmt::Break(None) => depth == 0,
Stmt::Break(Some(l)) => Some(l.as_str()) == lbl,
Stmt::Continue(_) | Stmt::Return(_) | Stmt::Throw(_) => false,
Stmt::Goto(_) => true,
Stmt::Block(v) => v.iter().any(|x| has_break_exiting(x, lbl, depth)),
Stmt::If {
then_stmt,
else_stmt,
..
} => {
has_break_exiting(then_stmt, lbl, depth)
|| else_stmt
.as_deref()
.map(|e| has_break_exiting(e, lbl, depth))
.unwrap_or(false)
}
Stmt::While { body, .. }
| Stmt::DoWhile { body, .. }
| Stmt::For { body, .. }
| Stmt::ForEach { body, .. } => has_break_exiting(body, lbl, depth + 1),
Stmt::Switch { cases, default, .. } => {
cases
.iter()
.any(|c| c.body.iter().any(|x| has_break_exiting(x, lbl, depth + 1)))
|| default
.as_deref()
.map(|d| has_break_exiting(d, lbl, depth + 1))
.unwrap_or(false)
}
Stmt::Try {
body,
catches,
finally,
}
| Stmt::TryWithResources {
body,
catches,
finally,
..
} => {
has_break_exiting(body, lbl, depth)
|| catches
.iter()
.any(|c| has_break_exiting(&c.body, lbl, depth))
|| finally
.as_deref()
.map(|f| has_break_exiting(f, lbl, depth))
.unwrap_or(false)
}
Stmt::Synchronized { body, .. } => has_break_exiting(body, lbl, depth),
Stmt::Labeled { body, .. } => has_break_exiting(body, lbl, depth),
Stmt::ExprStmt(_)
| Stmt::LocalDef { .. }
| Stmt::Assert { .. }
| Stmt::TernaryValue { .. }
| Stmt::MonitorEnter(_)
| Stmt::MonitorExit(_)
| Stmt::Comment(_) => false,
_ => true,
}
}
pub const ASSERT_FIELD: &str = "$jcdcAssertionsDisabled";
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::build::int_const;
fn while_true(body: Stmt) -> Stmt {
Stmt::While {
cond: int_const(1),
body: Box::new(body),
}
}
#[test]
fn infinite_while_without_break_is_a_dead_end() {
assert!(dead_end_infinite_while(&while_true(Stmt::Block(vec![]))));
let nested = Stmt::Block(vec![while_true(Stmt::Block(vec![Stmt::Break(None)]))]);
assert!(dead_end_infinite_while(&while_true(nested)));
assert!(!dead_end_infinite_while(&while_true(Stmt::Block(vec![
Stmt::Break(None)
]))));
assert!(dead_end_infinite_while(&Stmt::Labeled {
label: "L".into(),
body: Box::new(while_true(Stmt::Block(vec![Stmt::Break(Some(
"outer".into()
))]))),
}));
}
}