Skip to main content

jdc_core/
analysis.rs

1//! Machine-neutral statement/CFG predicates shared by the structuring and
2//! emission layers.
3//!
4//! These answer questions about *Java control flow*, not about any bytecode:
5//! "does this loop complete normally?", "does this statement leave a block?".
6//! A front-end never needs to override them.
7
8use crate::ir::expr::{ConstVal, Expr};
9use crate::ir::stmt::Stmt;
10
11/// True when a `while (true)` loop (optionally labeled) can NOT complete
12/// normally, so the statements after it are unreachable and a decompiler may
13/// truncate them.
14///
15/// JLS 14.14: `while (true)` completes normally iff its body holds a reachable
16/// break that exits IT — an unlabeled break not captured by an inner breakable
17/// construct, or `break L` where `L` is this loop's own label. A labeled break
18/// to any other label (inner or outer) completes the loop abruptly.
19pub fn dead_end_infinite_while(s: &Stmt) -> bool {
20    let (label, body, cond) = match s {
21        Stmt::While { cond, body } => (None, body.as_ref(), cond),
22        Stmt::Labeled { label, body } => match &**body {
23            Stmt::While { cond, body } => (Some(label.as_str()), body.as_ref(), cond),
24            _ => return false,
25        },
26        _ => return false,
27    };
28    if !matches!(cond, Expr::Const(ConstVal::Int(1))) {
29        return false;
30    }
31    !has_break_exiting(body, label, 0)
32}
33
34/// True when `s` contains a break that exits the loop labeled `lbl`
35/// (`None` = the innermost unlabeled loop).
36fn has_break_exiting(s: &Stmt, lbl: Option<&str>, depth: usize) -> bool {
37    match s {
38        Stmt::Break(None) => depth == 0,
39        // Only this loop's OWN label completes it; any other labeled break
40        // exits abruptly (JLS 14.14/14.17).
41        Stmt::Break(Some(l)) => Some(l.as_str()) == lbl,
42        Stmt::Continue(_) | Stmt::Return(_) | Stmt::Throw(_) => false,
43        Stmt::Goto(_) => true,
44        Stmt::Block(v) => v.iter().any(|x| has_break_exiting(x, lbl, depth)),
45        Stmt::If { then_stmt, else_stmt, .. } => {
46            has_break_exiting(then_stmt, lbl, depth)
47                || else_stmt
48                    .as_deref()
49                    .map(|e| has_break_exiting(e, lbl, depth))
50                    .unwrap_or(false)
51        }
52        Stmt::While { body, .. }
53        | Stmt::DoWhile { body, .. }
54        | Stmt::For { body, .. }
55        | Stmt::ForEach { body, .. } => has_break_exiting(body, lbl, depth + 1),
56        Stmt::Switch { cases, default, .. } => {
57            cases
58                .iter()
59                .any(|c| c.body.iter().any(|x| has_break_exiting(x, lbl, depth + 1)))
60                || default
61                    .as_deref()
62                    .map(|d| has_break_exiting(d, lbl, depth + 1))
63                    .unwrap_or(false)
64        }
65        Stmt::Try { body, catches, finally }
66        | Stmt::TryWithResources { body, catches, finally, .. } => {
67            has_break_exiting(body, lbl, depth)
68                || catches.iter().any(|c| has_break_exiting(&c.body, lbl, depth))
69                || finally
70                    .as_deref()
71                    .map(|f| has_break_exiting(f, lbl, depth))
72                    .unwrap_or(false)
73        }
74        Stmt::Synchronized { body, .. } => has_break_exiting(body, lbl, depth),
75        Stmt::Labeled { body, .. } => has_break_exiting(body, lbl, depth),
76        // Plain leaf statements never contain a break.
77        Stmt::ExprStmt(_)
78        | Stmt::LocalDef { .. }
79        | Stmt::Assert { .. }
80        | Stmt::TernaryValue { .. }
81        | Stmt::MonitorEnter(_)
82        | Stmt::MonitorExit(_)
83        | Stmt::Comment(_) => false,
84        // Opaque shapes (raw labels, inline class decls, anything new): keep
85        // truncation off when an escape cannot be ruled out.
86        _ => true,
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::ir::build::int_const;
94
95    fn while_true(body: Stmt) -> Stmt {
96        Stmt::While { cond: int_const(1), body: Box::new(body) }
97    }
98
99    #[test]
100    fn infinite_while_without_break_is_a_dead_end() {
101        assert!(dead_end_infinite_while(&while_true(Stmt::Block(vec![]))));
102        // A break inside a NESTED loop does not exit the outer while(true).
103        let nested = Stmt::Block(vec![while_true(Stmt::Block(vec![Stmt::Break(None)]))]);
104        assert!(dead_end_infinite_while(&while_true(nested)));
105        // A direct break makes the loop completable.
106        assert!(!dead_end_infinite_while(&while_true(Stmt::Block(vec![Stmt::Break(None)]))));
107        // A labeled break to an outer label exits abruptly.
108        assert!(dead_end_infinite_while(&Stmt::Labeled {
109            label: "L".into(),
110            body: Box::new(while_true(Stmt::Block(vec![Stmt::Break(Some("outer".into()))]))),
111        }));
112    }
113}
114/// The renamed `$assertionsDisabled` field (javac reserves the original name).
115pub const ASSERT_FIELD: &str = "$jcdcAssertionsDisabled";