Skip to main content

jdc_core/
var.rs

1//! Local variable allocation: maps bytecode slots (possibly reused across
2//! disjoint pc ranges) to named variables.
3
4//! Variable table: what the front-end knows about a method's locals.
5//!
6//! The *data* is machine-neutral (a name and a type per variable); how it is
7//! filled is not. A JVM front-end derives it from `LocalVariableTable`s; a DEX
8//! front-end synthesizes it from register live ranges (release APKs usually
9//! carry no local names at all).
10
11use std::collections::HashSet;
12
13use crate::ir::expr::TypeRef;
14use crate::types::{GenericType, JavaType};
15
16#[derive(Debug, Clone)]
17pub struct VarInfo {
18    pub id: u32,
19    pub slot: u16,
20    pub name: String,
21    pub ty: TypeRef,
22    pub is_param: bool,
23    /// pc range where this variable is live (from LVT; method end when open).
24    pub range_start: u16,
25    pub range_end: u16,
26    /// Set when the name was synthesized (no LVT entry).
27    pub synthetic_name: bool,
28}
29
30#[derive(Debug, Clone, Default)]
31pub struct VarTable {
32    pub vars: Vec<VarInfo>,
33    /// Per slot: (range_start, range_end, var_id), sorted by range_start.
34    pub by_slot: Vec<Vec<(u16, u16, u32)>>,
35    pub has_lvt: bool,
36    /// Synthetic stack-merge variables (need declaration hoisting).
37    pub stack_vars: Vec<u32>,
38    /// Merge variables whose branches disagreed on type (or carried a
39    /// generic/plain-Object value); they must stay Object so every branch
40    /// assignment type-checks. Evidence inference may not narrow them.
41    pub wide_stack_vars: HashSet<u32>,
42    /// Exception-table handler start pcs: an LVT range beginning at one
43    /// (or at its astore + 1/2) is a catch parameter binding. Such
44    /// ranges never receive forward store attribution — a store just
45    /// BEFORE a handler initializes the try-body variable, not the
46    /// catch param (jdk11 HostnameChecker.matchDNS: `sni = new
47    /// SNIHostName(..)` at pc 8 landed on `iae` whose handler range
48    /// starts at 14, within the 8-byte lead-in — SNIHostName无法转换为
49    /// IllegalArgumentException).
50    pub handler_starts: Vec<u16>,
51}
52
53/// Pull the typed Code attribute out of a method, if any.
54
55impl VarTable {
56    pub fn add_stack_var(&mut self, slot: u16, name: String, ty: TypeRef) -> u32 {
57        let id = self.add(slot, name, ty, false, 0, u16::MAX, true);
58        self.stack_vars.push(id);
59        id
60    }
61
62    /// Allocate a synthetic variable that is NOT a stack-merge temp (e.g. a
63    /// catch parameter); it must not be hoisted by the stack-var pass.
64    /// Add a new variable identity for slot-reuse splitting (post-build;
65    /// not registered in pc-range lookups).
66    pub fn add_split(&mut self, slot: u16, name: String, ty: TypeRef) -> u32 {
67        let id = self.vars.len() as u32;
68        self.vars.push(VarInfo {
69            id,
70            slot,
71            name,
72            ty,
73            is_param: false,
74            range_start: 0,
75            range_end: u16::MAX,
76            synthetic_name: true,
77        });
78        id
79    }
80
81    pub fn add_catch_var(&mut self, slot: u16, name: String, ty: TypeRef) -> u32 {
82        self.add(slot, name, ty, false, 0, u16::MAX, true)
83    }
84
85    fn add(
86        &mut self,
87        slot: u16,
88        name: String,
89        ty: TypeRef,
90        is_param: bool,
91        rs: u16,
92        re: u16,
93        synth: bool,
94    ) -> u32 {
95        let id = self.vars.len() as u32;
96        self.vars.push(VarInfo {
97            id,
98            slot,
99            name,
100            ty,
101            is_param,
102            range_start: rs,
103            range_end: re,
104            synthetic_name: synth,
105        });
106        while self.by_slot.len() <= slot as usize {
107            self.by_slot.push(Vec::new());
108        }
109        self.by_slot[slot as usize].push((rs, re, id));
110        id
111    }
112
113    /// Resolve the variable live at (pc, slot). Falls back to the nearest
114    /// preceding range, then to any var on the slot.
115    pub fn at(&self, slot: u16, pc: u16) -> Option<u32> {
116        let segs = self.by_slot.get(slot as usize)?;
117        for (rs, re, id) in segs {
118            if pc >= *rs && pc < *re {
119                return Some(*id);
120            }
121        }
122        // javac often starts an LVT range just AFTER the storing
123        // instruction; attribute a nearby access to the next range before
124        // falling back to the previous one (keeps slot-reuse splits like
125        // `byte[] dst` / `int b` on one slot apart). Never forward-
126        // attribute into a catch-param range (starts at a handler pc or
127        // its astore+1/+2): the store before a handler initializes the
128        // try-body variable (jdk11 HostnameChecker.matchDNS `sni = new
129        // SNIHostName(..)` was typed IllegalArgumentException).
130        for (rs, _, id) in segs {
131            if *rs > pc
132                && *rs - pc <= 8
133                && !self
134                    .handler_starts
135                    .iter()
136                    .any(|h| *rs == *h || *rs == h + 1 || *rs == h + 2)
137            {
138                return Some(*id);
139            }
140        }
141        let mut best: Option<u32> = None;
142        for (rs, _, id) in segs {
143            if *rs <= pc {
144                best = Some(*id);
145            }
146        }
147        best.or_else(|| segs.first().map(|(_, _, id)| *id))
148    }
149
150    /// The single variable covering a slot (when the slot is not reused).
151    pub fn sole_on_slot(&self, slot: u16) -> Option<u32> {
152        let segs = self.by_slot.get(slot as usize)?;
153        if segs.len() == 1 {
154            Some(segs[0].2)
155        } else {
156            None
157        }
158    }
159
160    pub fn vars_on_slot(&self, slot: u16) -> &[(u16, u16, u32)] {
161        self.by_slot
162            .get(slot as usize)
163            .map(|v| v.as_slice())
164            .unwrap_or(&[])
165    }
166
167    pub fn var(&self, id: u32) -> &VarInfo {
168        static DUMMY: std::sync::OnceLock<VarInfo> = std::sync::OnceLock::new();
169        self.vars.get(id as usize).unwrap_or_else(|| {
170            DUMMY.get_or_init(|| VarInfo {
171                id,
172                slot: u16::MAX,
173                name: format!("var{}", id),
174                ty: TypeRef::J(crate::types::JavaType::Int),
175                is_param: false,
176                range_start: 0,
177                range_end: 0,
178                synthetic_name: true,
179            })
180        })
181    }
182
183    pub fn var_mut(&mut self, id: u32) -> &mut VarInfo {
184        &mut self.vars[id as usize]
185    }
186}
187
188pub fn sig_type_at(
189    lvtt: &[(u16, u16, String, u16)],
190    start: u16,
191    slot: u16,
192    base: &JavaType,
193) -> Option<TypeRef> {
194    lvtt.iter()
195        .find(|(s, _, _, sl)| *s == start && *sl == slot)
196        .and_then(|(_, _, sig, _)| crate::types::parse_field_signature(sig).map(TypeRef::G))
197        // Reject signatures that cannot describe the descriptor type (a
198        // merged LVT range can accidentally align with an unrelated
199        // generic entry, e.g. a type variable E over a concrete class).
200        .filter(|tr| sig_matches_base(tr, base))
201}
202
203pub fn sig_matches_base(tr: &TypeRef, base: &JavaType) -> bool {
204    let g = match tr {
205        TypeRef::G(g) => g,
206        TypeRef::J(_) => return true,
207    };
208    match g {
209        // A type variable erases to its bound (Object or a concrete
210        // class), so any reference descriptor is compatible.
211        crate::types::GenericType::TypeVar(_) => matches!(base, JavaType::Object(_)),
212        crate::types::GenericType::Class(cs) => {
213            matches!(base, JavaType::Object(n) if n.as_ref() == cs.internal_name())
214        }
215        crate::types::GenericType::Primitive(c) => base.primitive_char() == Some(*c),
216        crate::types::GenericType::Array(_) => matches!(base, JavaType::Array(_)),
217        crate::types::GenericType::Wildcard(_) => false,
218    }
219}
220
221/// Placeholder type used when the builder cannot infer a better one.
222pub fn unknown_ref_type() -> TypeRef {
223    TypeRef::G(GenericType::Class(crate::types::ClassSig {
224        package: "java/lang".into(),
225        parts: vec![crate::types::ClassSigPart {
226            name: "Object".into(),
227            args: vec![],
228        }],
229    }))
230}