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 {
46            then_stmt,
47            else_stmt,
48            ..
49        } => {
50            has_break_exiting(then_stmt, lbl, depth)
51                || else_stmt
52                    .as_deref()
53                    .map(|e| has_break_exiting(e, lbl, depth))
54                    .unwrap_or(false)
55        }
56        Stmt::While { body, .. }
57        | Stmt::DoWhile { body, .. }
58        | Stmt::For { body, .. }
59        | Stmt::ForEach { body, .. } => has_break_exiting(body, lbl, depth + 1),
60        Stmt::Switch { cases, default, .. } => {
61            cases
62                .iter()
63                .any(|c| c.body.iter().any(|x| has_break_exiting(x, lbl, depth + 1)))
64                || default
65                    .as_deref()
66                    .map(|d| has_break_exiting(d, lbl, depth + 1))
67                    .unwrap_or(false)
68        }
69        Stmt::Try {
70            body,
71            catches,
72            finally,
73        }
74        | Stmt::TryWithResources {
75            body,
76            catches,
77            finally,
78            ..
79        } => {
80            has_break_exiting(body, lbl, depth)
81                || catches
82                    .iter()
83                    .any(|c| has_break_exiting(&c.body, lbl, depth))
84                || finally
85                    .as_deref()
86                    .map(|f| has_break_exiting(f, lbl, depth))
87                    .unwrap_or(false)
88        }
89        Stmt::Synchronized { body, .. } => has_break_exiting(body, lbl, depth),
90        Stmt::Labeled { body, .. } => has_break_exiting(body, lbl, depth),
91        // Plain leaf statements never contain a break.
92        Stmt::ExprStmt(_)
93        | Stmt::LocalDef { .. }
94        | Stmt::Assert { .. }
95        | Stmt::TernaryValue { .. }
96        | Stmt::MonitorEnter(_)
97        | Stmt::MonitorExit(_)
98        | Stmt::Comment(_) => false,
99        // Opaque shapes (raw labels, inline class decls, anything new): keep
100        // truncation off when an escape cannot be ruled out.
101        _ => true,
102    }
103}
104
105/// The renamed `$assertionsDisabled` field (javac reserves the original name).
106pub const ASSERT_FIELD: &str = "$jcdcAssertionsDisabled";
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::ir::build::int_const;
112
113    fn while_true(body: Stmt) -> Stmt {
114        Stmt::While {
115            cond: int_const(1),
116            body: Box::new(body),
117        }
118    }
119
120    #[test]
121    fn infinite_while_without_break_is_a_dead_end() {
122        assert!(dead_end_infinite_while(&while_true(Stmt::Block(vec![]))));
123        // A break inside a NESTED loop does not exit the outer while(true).
124        let nested = Stmt::Block(vec![while_true(Stmt::Block(vec![Stmt::Break(None)]))]);
125        assert!(dead_end_infinite_while(&while_true(nested)));
126        // A direct break makes the loop completable.
127        assert!(!dead_end_infinite_while(&while_true(Stmt::Block(vec![
128            Stmt::Break(None)
129        ]))));
130        // A labeled break to an outer label exits abruptly.
131        assert!(dead_end_infinite_while(&Stmt::Labeled {
132            label: "L".into(),
133            body: Box::new(while_true(Stmt::Block(vec![Stmt::Break(Some(
134                "outer".into()
135            ))]))),
136        }));
137    }
138}