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(&mut self, slot: u16, name: String, ty: TypeRef, is_param: bool, rs: u16, re: u16, synth: bool) -> u32 {
86        let id = self.vars.len() as u32;
87        self.vars.push(VarInfo { id, slot, name, ty, is_param, range_start: rs, range_end: re, synthetic_name: synth });
88        while self.by_slot.len() <= slot as usize {
89            self.by_slot.push(Vec::new());
90        }
91        self.by_slot[slot as usize].push((rs, re, id));
92        id
93    }
94
95    /// Resolve the variable live at (pc, slot). Falls back to the nearest
96    /// preceding range, then to any var on the slot.
97    pub fn at(&self, slot: u16, pc: u16) -> Option<u32> {
98        let segs = self.by_slot.get(slot as usize)?;
99        for (rs, re, id) in segs {
100            if pc >= *rs && pc < *re {
101                return Some(*id);
102            }
103        }
104        // javac often starts an LVT range just AFTER the storing
105        // instruction; attribute a nearby access to the next range before
106        // falling back to the previous one (keeps slot-reuse splits like
107        // `byte[] dst` / `int b` on one slot apart). Never forward-
108        // attribute into a catch-param range (starts at a handler pc or
109        // its astore+1/+2): the store before a handler initializes the
110        // try-body variable (jdk11 HostnameChecker.matchDNS `sni = new
111        // SNIHostName(..)` was typed IllegalArgumentException).
112        for (rs, _, id) in segs {
113            if *rs > pc
114                && *rs - pc <= 8
115                && !self.handler_starts.iter().any(|h| {
116                    *rs == *h || *rs == h + 1 || *rs == h + 2
117                })
118            {
119                return Some(*id);
120            }
121        }
122        let mut best: Option<u32> = None;
123        for (rs, _, id) in segs {
124            if *rs <= pc {
125                best = Some(*id);
126            }
127        }
128        best.or_else(|| segs.first().map(|(_, _, id)| *id))
129    }
130
131    /// The single variable covering a slot (when the slot is not reused).
132    pub fn sole_on_slot(&self, slot: u16) -> Option<u32> {
133        let segs = self.by_slot.get(slot as usize)?;
134        if segs.len() == 1 {
135            Some(segs[0].2)
136        } else {
137            None
138        }
139    }
140
141    pub fn vars_on_slot(&self, slot: u16) -> &[(u16, u16, u32)] {
142        self.by_slot.get(slot as usize).map(|v| v.as_slice()).unwrap_or(&[])
143    }
144
145    pub fn var(&self, id: u32) -> &VarInfo {
146        static DUMMY: std::sync::OnceLock<VarInfo> = std::sync::OnceLock::new();
147        self.vars.get(id as usize).unwrap_or_else(|| {
148            DUMMY.get_or_init(|| VarInfo {
149                id,
150                slot: u16::MAX,
151                name: format!("var{}", id),
152                ty: TypeRef::J(crate::types::JavaType::Int),
153                is_param: false,
154                range_start: 0,
155                range_end: 0,
156                synthetic_name: true,
157            })
158        })
159    }
160
161    pub fn var_mut(&mut self, id: u32) -> &mut VarInfo {
162        &mut self.vars[id as usize]
163    }
164}
165
166pub fn sig_type_at(
167    lvtt: &[(u16, u16, String, u16)],
168    start: u16,
169    slot: u16,
170    base: &JavaType,
171) -> Option<TypeRef> {
172    lvtt
173        .iter()
174        .find(|(s, _, _, sl)| *s == start && *sl == slot)
175        .and_then(|(_, _, sig, _)| crate::types::parse_field_signature(sig).map(TypeRef::G))
176        // Reject signatures that cannot describe the descriptor type (a
177        // merged LVT range can accidentally align with an unrelated
178        // generic entry, e.g. a type variable E over a concrete class).
179        .filter(|tr| sig_matches_base(tr, base))
180}
181
182pub fn sig_matches_base(tr: &TypeRef, base: &JavaType) -> bool {
183    let g = match tr {
184        TypeRef::G(g) => g,
185        TypeRef::J(_) => return true,
186    };
187    match g {
188        // A type variable erases to its bound (Object or a concrete
189        // class), so any reference descriptor is compatible.
190        crate::types::GenericType::TypeVar(_) => matches!(base, JavaType::Object(_)),
191        crate::types::GenericType::Class(cs) => {
192            matches!(base, JavaType::Object(n) if *n == cs.internal_name())
193        }
194        crate::types::GenericType::Primitive(c) => base.primitive_char() == Some(*c),
195        crate::types::GenericType::Array(_) => matches!(base, JavaType::Array(_)),
196        crate::types::GenericType::Wildcard(_) => false,
197    }
198}
199
200/// Placeholder type used when the builder cannot infer a better one.
201pub fn unknown_ref_type() -> TypeRef {
202    TypeRef::G(GenericType::Class(crate::types::ClassSig {
203        package: "java/lang".into(),
204        parts: vec![crate::types::ClassSigPart { name: "Object".into(), args: vec![] }],
205    }))
206}