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