Skip to main content

ddc_dec/
ctx.rs

1//! The DEX front-end's implementation of `jdc_core::Ctx`.
2//!
3//! Every method here answers a *semantic* question about class metadata
4//! using the pooled DEX class table; generics are absent (erased), so all
5//! generic-aware queries answer "unknown" and the core degrades gracefully.
6
7use jdc_core::ir::expr::{Expr, TypeRef};
8use jdc_core::types::{
9    ClassAccessFlags, FieldAccessFlags, GenericType, JavaType, MethodAccessFlags, MethodDescriptor,
10};
11use jdc_core::{Ctx, Family, NestedClass, NestedKind};
12
13use crate::access::*;
14use crate::{desc_type, DexPool, PoolClass};
15
16/// Front-end context handed to the core's structurer and printer.
17pub struct DexCtx<'a> {
18    pub pool: &'a DexPool,
19    pub class: &'a PoolClass,
20}
21
22impl<'a> DexCtx<'a> {
23    pub fn new(pool: &'a DexPool, class: &'a PoolClass) -> Self {
24        DexCtx { pool, class }
25    }
26
27    fn find_class(&self, internal: &str) -> Option<&PoolClass> {
28        self.pool.get(internal)
29    }
30
31    /// Structural inner-class evidence: an instance field whose type IS
32    /// the enclosing class (the this$0 outer reference; type-based so it
33    /// survives field renames).
34    fn holds_outer_ref(&self, internal: &str, pc: &PoolClass) -> bool {
35        let Some(outer) = self.find_outer(internal) else {
36            return false;
37        };
38        pc.instance_fields.iter().any(|f| {
39            f.desc
40                .strip_prefix('L')
41                .and_then(|d| d.strip_suffix(';'))
42                .is_some_and(|ty| ty == outer)
43        })
44    }
45
46    /// Nesting evidence (see `find_outer_name`).
47    fn outer_of(&self, internal: &str) -> Option<String> {
48        crate::find_outer_name(self.pool, internal)
49    }
50
51    fn class_access_flags(&self, internal: &str) -> ClassAccessFlags {
52        let mut f = ClassAccessFlags::empty();
53        let Some(pc) = self.find_class(internal) else {
54            return f;
55        };
56        let a = pc.access;
57        if a & ACC_PUBLIC != 0 {
58            f |= ClassAccessFlags::PUBLIC;
59        }
60        if a & ACC_FINAL != 0 {
61            f |= ClassAccessFlags::FINAL;
62        }
63        if a & ACC_INTERFACE != 0 {
64            f |= ClassAccessFlags::INTERFACE;
65        }
66        if a & ACC_ABSTRACT != 0 {
67            f |= ClassAccessFlags::ABSTRACT;
68        }
69        if a & ACC_SYNTHETIC != 0 {
70            f |= ClassAccessFlags::SYNTHETIC;
71        }
72        if a & ACC_ANNOTATION != 0 {
73            f |= ClassAccessFlags::ANNOTATION;
74        }
75        if a & ACC_ENUM != 0 {
76            f |= ClassAccessFlags::ENUM;
77        }
78        f
79    }
80}
81
82impl<'a> Ctx for DexCtx<'a> {
83    fn pool_id(&self) -> u64 {
84        // Printer::shorten memo cache key: every DexCtx in a run shares
85        // one DexPool, and shorten's ctx queries (has_class/find_outer/
86        // class_bases) are pool-level — one cache entry set per pool.
87        self.pool as *const DexPool as u64
88    }
89
90    fn obscured_simple(&self, internal: &str) -> Option<String> {
91        crate::classdec::obscured_render_pub(internal)
92    }
93
94    fn class_name(&self) -> &str {
95        &self.class.name
96    }
97
98    fn source_level(&self) -> u16 {
99        52
100    }
101
102    fn find_outer(&self, internal: &str) -> Option<String> {
103        if internal == self.class.name {
104            return self.outer_of(internal);
105        }
106        self.outer_of(internal)
107    }
108
109    fn family(&self, root: &str) -> Family {
110        let mut fam = Family {
111            root: root.to_string(),
112            ..Default::default()
113        };
114        // Cached child index: BFS over the `$` chain from `root`.
115        let mut queue: std::collections::VecDeque<String> =
116            self.pool.children_of(root).iter().cloned().collect();
117        let mut seen: jdc_core::FxHashSet<String> = queue.iter().cloned().collect();
118        while let Some(name) = queue.pop_front() {
119            for c in self.pool.children_of(&name) {
120                if seen.insert(c.clone()) {
121                    queue.push_back(c.clone());
122                }
123            }
124            let Some(_) = self.find_class(&name) else {
125                continue;
126            };
127            let root_prefix = format!("{}$", root);
128            let rest: String = if name.starts_with(&root_prefix) {
129                name[root_prefix.len()..].to_string()
130            } else {
131                name.rsplit('$').next().unwrap_or(&name).to_string()
132            };
133            let simple = rest.rsplit('$').next().unwrap_or(&rest).to_string();
134            let kind = classify_nested(&rest);
135            let access = self.class_access_flags(&name);
136            fam.nested.insert(
137                name.clone(),
138                NestedClass {
139                    name: name.clone(),
140                    simple,
141                    kind,
142                    access,
143                    sig_header: None,
144                },
145            );
146            match kind {
147                NestedKind::Anonymous => {
148                    fam.anonymous.insert(name.clone());
149                }
150                NestedKind::Local => {
151                    fam.locals.insert(name.clone());
152                }
153                NestedKind::Lambda => {
154                    fam.lambdas.insert(name.clone());
155                }
156                NestedKind::Member => {}
157            }
158        }
159        fam
160    }
161
162    fn nested_is_static(&self, internal: &str) -> bool {
163        match self.find_class(internal) {
164            // ACC_STATIC (annotation evidence) OR the structural signal:
165            // no instance field typed as the outer class. javac ALWAYS
166            // gives a non-static inner class an enclosing-instance field
167            // (this$0), and the field TYPE survives obfuscation that
168            // renames the field itself. Plain d8 output carries no
169            // nesting annotations at all — without the structural
170            // fallback every static nested class rendered as an inner
171            // one (`str.new Report(...)` swallowing the first ctor arg).
172            Some(pc) => pc.is_static_nested() || !self.holds_outer_ref(internal, pc),
173            None => true,
174        }
175    }
176
177    fn class_has_this0(&self, internal: &str) -> bool {
178        // Inner classes without ACC_STATIC carry an enclosing instance.
179        match self.find_class(internal) {
180            Some(pc) => !pc.is_static_nested() && self.holds_outer_ref(internal, pc),
181            None => false,
182        }
183    }
184
185    fn is_subtype_of(&self, sub: &JavaType, sup: &str) -> bool {
186        if let JavaType::Object(n) = sub {
187            self.pool.is_subtype(n, sup)
188        } else {
189            false
190        }
191    }
192
193    fn is_interface(&self, internal: &str) -> bool {
194        self.find_class(internal)
195            .map(|pc| pc.is_interface())
196            .unwrap_or(false)
197    }
198
199    fn is_sealed(&self, _internal: &str) -> bool {
200        false
201    }
202
203    fn super_name(&self, internal: &str) -> Option<String> {
204        self.find_class(internal)
205            .and_then(|pc| pc.super_name.clone())
206    }
207
208    fn has_class(&self, internal: &str) -> bool {
209        self.pool.get(internal).is_some() || internal == self.class.name
210    }
211
212    fn class_supers_args(
213        &self,
214        internal: &str,
215        _args: &[GenericType],
216    ) -> Vec<(String, Vec<GenericType>)> {
217        let mut out = Vec::new();
218        let mut cur = self.find_class(internal).map(|pc| pc.name.clone());
219        let mut hops = 0;
220        while let Some(name) = cur {
221            hops += 1;
222            if hops > 64 {
223                break;
224            }
225            if name == "java/lang/Object" {
226                break;
227            }
228            out.push((name.clone(), Vec::new()));
229            cur = self.find_class(&name).and_then(|pc| pc.super_name.clone());
230        }
231        out
232    }
233
234    fn class_bases(&self, internal: &str) -> Option<(Vec<String>, Option<String>)> {
235        let pc = self.find_class(internal)?;
236        Some((pc.interfaces.clone(), pc.super_name.clone()))
237    }
238
239    fn field_flags(&self, internal: &str, name: &str) -> Option<FieldAccessFlags> {
240        let pc = self.find_class(internal)?;
241        let raw = pc
242            .static_fields
243            .iter()
244            .chain(pc.instance_fields.iter())
245            .find(|f| f.name == name)?
246            .access;
247        Some(raw_field_flags(raw))
248    }
249
250    fn method_flags(&self, internal: &str, name: &str, desc: &str) -> Option<MethodAccessFlags> {
251        let pc = self.find_class(internal)?;
252        let m = pc.find_method(name, desc)?;
253        Some(raw_method_flags(m.access))
254    }
255
256    fn declares_field(&self, internal: &str, name: &str) -> bool {
257        self.find_class(internal)
258            .map(|pc| pc.field_flags_of(name).is_some())
259            .unwrap_or(false)
260    }
261
262    fn declares_method_named(&self, internal: &str, name: &str) -> bool {
263        self.find_class(internal)
264            .map(|pc| pc.all_methods().any(|m| &*m.name == name))
265            .unwrap_or(false)
266    }
267
268    fn is_generic_call(&self, _e: &Expr) -> bool {
269        false
270    }
271
272    fn generic_call_formals(&self, _e: &Expr) -> Option<(Vec<GenericType>, Vec<String>)> {
273        None
274    }
275
276    fn polymorphic_ret_cast(
277        &self,
278        _cls: &str,
279        _name: &str,
280        _desc: &MethodDescriptor,
281    ) -> Option<JavaType> {
282        None
283    }
284
285    fn ctor_formals_by_arity(&self, internal: &str, arity: usize) -> Option<Vec<GenericType>> {
286        let pc = self.find_class(internal)?;
287        let ctors = pc.ctors_by_arity(arity);
288        let m = ctors.first()?;
289        let md = m.parsed_desc()?;
290        Some(md.args.iter().map(java_type_to_generic).collect())
291    }
292
293    fn ctor_param_types(
294        &self,
295        internal: &str,
296        skip: usize,
297        n: usize,
298        args: &[Expr],
299    ) -> Option<Vec<JavaType>> {
300        let _ = args;
301        let pc = self.find_class(internal)?;
302        let ctors = pc.ctors_by_arity(skip + n);
303        let m = ctors.first()?;
304        let md = m.parsed_desc()?;
305        Some(md.args.iter().skip(skip).cloned().collect())
306    }
307
308    fn outer_param_via_super(&self, _internal: &str) -> bool {
309        false
310    }
311
312    fn nested_method(
313        &self,
314        _l: &jdc_core::ir::expr::LambdaExpr,
315        _outer_vt: &jdc_core::var::VarTable,
316    ) -> Option<jdc_core::MethodBody> {
317        None
318    }
319}
320
321/// `$`-suffix classification (javac conventions d8 preserves).
322fn classify_nested(rest: &str) -> NestedKind {
323    // rest is the text after `Outer$`.
324    let tail = rest.rsplit('$').next().unwrap_or(rest);
325    if rest.starts_with("-$$Lambda$") || tail.starts_with("-$$Lambda$") {
326        return NestedKind::Lambda;
327    }
328    if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
329        return NestedKind::Anonymous;
330    }
331    if tail.starts_with(|c: char| c.is_ascii_digit()) {
332        return NestedKind::Local;
333    }
334    NestedKind::Member
335}
336
337fn raw_field_flags(raw: u32) -> FieldAccessFlags {
338    let mut f = FieldAccessFlags::empty();
339    if raw & ACC_PUBLIC != 0 {
340        f |= FieldAccessFlags::PUBLIC;
341    }
342    if raw & ACC_PRIVATE != 0 {
343        f |= FieldAccessFlags::PRIVATE;
344    }
345    if raw & ACC_PROTECTED != 0 {
346        f |= FieldAccessFlags::PROTECTED;
347    }
348    if raw & ACC_STATIC != 0 {
349        f |= FieldAccessFlags::STATIC;
350    }
351    if raw & ACC_FINAL != 0 {
352        f |= FieldAccessFlags::FINAL;
353    }
354    if raw & ACC_SYNTHETIC != 0 {
355        f |= FieldAccessFlags::SYNTHETIC;
356    }
357    if raw & ACC_ENUM != 0 {
358        f |= FieldAccessFlags::ENUM;
359    }
360    f
361}
362
363fn raw_method_flags(raw: u32) -> MethodAccessFlags {
364    let mut f = MethodAccessFlags::empty();
365    if raw & ACC_PUBLIC != 0 {
366        f |= MethodAccessFlags::PUBLIC;
367    }
368    if raw & ACC_PRIVATE != 0 {
369        f |= MethodAccessFlags::PRIVATE;
370    }
371    if raw & ACC_PROTECTED != 0 {
372        f |= MethodAccessFlags::PROTECTED;
373    }
374    if raw & ACC_STATIC != 0 {
375        f |= MethodAccessFlags::STATIC;
376    }
377    if raw & ACC_FINAL != 0 {
378        f |= MethodAccessFlags::FINAL;
379    }
380    if raw & ACC_SYNCHRONIZED != 0 || raw & ACC_DECLARED_SYNCHRONIZED != 0 {
381        f |= MethodAccessFlags::SYNCHRONIZED;
382    }
383    if raw & ACC_BRIDGE != 0 {
384        f |= MethodAccessFlags::BRIDGE;
385    }
386    if raw & ACC_VARARGS != 0 {
387        f |= MethodAccessFlags::VARARGS;
388    }
389    if raw & ACC_NATIVE != 0 {
390        f |= MethodAccessFlags::NATIVE;
391    }
392    if raw & ACC_ABSTRACT != 0 {
393        f |= MethodAccessFlags::ABSTRACT;
394    }
395    if raw & ACC_STRICT != 0 {
396        f |= MethodAccessFlags::STRICT;
397    }
398    if raw & ACC_SYNTHETIC != 0 {
399        f |= MethodAccessFlags::SYNTHETIC;
400    }
401    f
402}
403
404/// Erased JavaType → generic type view (no signature data in DEX).
405pub fn java_type_to_generic(t: &JavaType) -> GenericType {
406    match t {
407        JavaType::Object(n) => GenericType::Class(class_sig_of(n)),
408        JavaType::Array(inner) => GenericType::Array(Box::new(java_type_to_generic(inner))),
409        other => GenericType::Primitive(other.primitive_char().unwrap_or('V')),
410    }
411}
412
413fn class_sig_of(internal: &str) -> jdc_core::types::ClassSig {
414    let (package, name) = match internal.rfind('/') {
415        Some(i) => (internal[..i].to_string(), internal[i + 1..].to_string()),
416        None => (String::new(), internal.to_string()),
417    };
418    jdc_core::types::ClassSig {
419        package,
420        parts: vec![jdc_core::types::ClassSigPart { name, args: vec![] }],
421    }
422}
423
424/// Render a type reference for signatures where the printer is not used.
425pub fn type_ref_of(desc: &str) -> TypeRef {
426    TypeRef::J(desc_type(desc))
427}