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    /// JLS inner-ctor shape: the synthetic outer instance rides as
76    /// formal-0 typed as the DIRECT enclosing class. Field evidence
77    /// alone misclassifies Kotlin lambda classes — kotlinc names the
78    /// captured `this` field this$0 too, but at an ARBITRARY param
79    /// position (lark SearchResultView$q: `(int $requestSeq,
80    /// SearchResultView this$0)`; the int capture was hijacked as the
81    /// qualified-new outer — `(this.w + 1).new q17(this)`, lark
82    /// 意外的类型 ×295). Mirrors the decl-side strip guard in classdec
83    /// (`d.args.first()` must BE the enclosing type).
84    fn outer_is_formal0(&self, internal: &str, pc: &PoolClass) -> bool {
85        let Some(outer) = self.find_outer(internal) else {
86            return false;
87        };
88        pc.all_methods().any(|m| {
89            &*m.name == "<init>"
90                && m.parsed_desc().is_some_and(|d| {
91                    d.args.first().is_some_and(|t| {
92                        matches!(t, JavaType::Object(n) if n.as_ref() == outer.as_str())
93                    })
94                })
95        })
96    }
97
98    fn holds_this0(&self, internal: &str, pc: &PoolClass) -> bool {
99        let Some(outer) = self.find_outer(internal) else {
100            return false;
101        };
102        pc.instance_fields.iter().any(|f| {
103            f.name.as_ref() == "this$0"
104                && f
105                    .desc
106                    .strip_prefix('L')
107                    .and_then(|d| d.strip_suffix(';'))
108                    .is_some_and(|ty| ty == outer)
109        })
110    }
111
112    /// Nesting evidence (see `find_outer_name`).
113    fn outer_of(&self, internal: &str) -> Option<String> {
114        crate::find_outer_name(self.pool, internal)
115    }
116
117    fn class_access_flags(&self, internal: &str) -> ClassAccessFlags {
118        let mut f = ClassAccessFlags::empty();
119        let Some(pc) = self.find_class(internal) else {
120            return f;
121        };
122        let a = pc.access;
123        if a & ACC_PUBLIC != 0 {
124            f |= ClassAccessFlags::PUBLIC;
125        }
126        if a & ACC_FINAL != 0 {
127            f |= ClassAccessFlags::FINAL;
128        }
129        if a & ACC_INTERFACE != 0 {
130            f |= ClassAccessFlags::INTERFACE;
131        }
132        if a & ACC_ABSTRACT != 0 {
133            f |= ClassAccessFlags::ABSTRACT;
134        }
135        if a & ACC_SYNTHETIC != 0 {
136            f |= ClassAccessFlags::SYNTHETIC;
137        }
138        if a & ACC_ANNOTATION != 0 {
139            f |= ClassAccessFlags::ANNOTATION;
140        }
141        if a & ACC_ENUM != 0 {
142            f |= ClassAccessFlags::ENUM;
143        }
144        f
145    }
146}
147
148impl<'a> Ctx for DexCtx<'a> {
149    fn pool_id(&self) -> u64 {
150        // Printer::shorten memo cache key: every DexCtx in a run shares
151        // one DexPool, and shorten's ctx queries (has_class/find_outer/
152        // class_bases) are pool-level — one cache entry set per pool.
153        self.pool as *const DexPool as u64
154    }
155
156    fn obscured_simple(&self, internal: &str) -> Option<String> {
157        crate::classdec::obscured_render_pub(internal)
158    }
159
160    fn obscured_member_escape(&self, owner: &str, member: &str) -> bool {
161        crate::classdec::obscured_member_escape_pub(owner, member)
162    }
163
164    fn class_name(&self) -> &str {
165        &self.class.name
166    }
167
168    fn source_level(&self) -> u16 {
169        52
170    }
171
172    fn find_outer(&self, internal: &str) -> Option<String> {
173        // ddcroot root-package relocation, SYMMETRIC with has_class's
174        // root_pkg clause: a relocated NESTED class (`ddcroot/agv$b`)
175        // must resolve its outer (`ddcroot/agv`) so nested_display dots
176        // the `$` into `ddcroot.agv.b`. has_class already un-prefixes
177        // `ddcroot/agv$b` to the pool key `agv$b` (→ true), but without
178        // this the outer walk fails (`ddcroot/agv` is not a pool key),
179        // so keep_dollar_pool saw (has_class, no-outer) and treated the
180        // genuine nestee as a flat `$` emission unit — rimet rendered
181        // `new ddcroot.agv$b(..)` / `ddcroot.agv$b[]`, unresolvable
182        // (找不到符号 类 agv$b/qse$b/bik$a ×1,100+). A relocated FLAT unit
183        // (R8 deleted the outer, lark UserCustomStatusExtraParams$*)
184        // still returns None here — the stripped outer is off-pool — so
185        // it correctly keeps its `$`.
186        if let Some(rp) = crate::root_pkg_display() {
187            if internal.len() > rp.len() + 1
188                && internal.starts_with(rp.as_str())
189                && internal.as_bytes()[rp.len()] == b'/'
190            {
191                let stripped = &internal[rp.len() + 1..];
192                return crate::find_outer_name(self.pool, stripped)
193                    .map(|o| format!("{rp}/{o}"));
194            }
195        }
196        self.outer_of(internal)
197    }
198
199    fn family(&self, root: &str) -> Family {
200        let mut fam = Family {
201            root: root.to_string(),
202            ..Default::default()
203        };
204        // Cached child index: BFS over the `$` chain from `root`.
205        let mut queue: std::collections::VecDeque<String> =
206            self.pool.children_of(root).iter().cloned().collect();
207        let mut seen: jdc_core::FxHashSet<String> = queue.iter().cloned().collect();
208        while let Some(name) = queue.pop_front() {
209            for c in self.pool.children_of(&name) {
210                if seen.insert(c.clone()) {
211                    queue.push_back(c.clone());
212                }
213            }
214            let Some(_) = self.find_class(&name) else {
215                continue;
216            };
217            let root_prefix = format!("{}$", root);
218            let rest: String = if name.starts_with(&root_prefix) {
219                name[root_prefix.len()..].to_string()
220            } else {
221                name.rsplit('$').next().unwrap_or(&name).to_string()
222            };
223            let simple = rest.rsplit('$').next().unwrap_or(&rest).to_string();
224            let kind = classify_nested(&rest);
225            let access = self.class_access_flags(&name);
226            fam.nested.insert(
227                name.clone(),
228                NestedClass {
229                    name: name.clone(),
230                    simple,
231                    kind,
232                    access,
233                    sig_header: None,
234                },
235            );
236            match kind {
237                NestedKind::Anonymous => {
238                    fam.anonymous.insert(name.clone());
239                }
240                NestedKind::Local => {
241                    fam.locals.insert(name.clone());
242                }
243                NestedKind::Lambda => {
244                    fam.lambdas.insert(name.clone());
245                }
246                NestedKind::Member => {}
247            }
248        }
249        fam
250    }
251
252    fn nested_is_static(&self, internal: &str) -> bool {
253        // ACC_STATIC (annotation evidence) OR the structural signal:
254        // no instance field typed as the outer class. javac ALWAYS
255        // gives a non-static inner class an enclosing-instance field
256        // (this$0), and the field TYPE survives obfuscation that
257        // renames the field itself. Plain d8 output carries no
258        // nesting annotations at all — without the structural
259        // fallback every static nested class rendered as an inner
260        // one (`str.new Report(...)` swallowing the first ctor arg).
261        // A this$0 field WITHOUT the formal-0 outer ctor param is a
262        // capture, not a JLS outer (Kotlin lambdas) — static too.
263        // Logic lives in the free `nested_is_static` below (shared
264        // with method.rs passes that run without a ctx).
265        match self.find_class(internal) {
266            Some(pc) => nested_is_static(self.pool, pc),
267            None => true,
268        }
269    }
270
271    fn class_has_this0(&self, internal: &str) -> bool {
272        // Inner classes without ACC_STATIC carry an enclosing instance.
273        match self.find_class(internal) {
274            Some(pc) => {
275                !pc.is_static_nested()
276                    && self.holds_this0(internal, pc)
277                    && self.outer_is_formal0(internal, pc)
278            }
279            None => false,
280        }
281    }
282
283    fn is_subtype_of(&self, sub: &JavaType, sup: &str) -> bool {
284        if let JavaType::Object(n) = sub {
285            self.pool.is_subtype(n, sup)
286        } else {
287            false
288        }
289    }
290
291    fn is_interface(&self, internal: &str) -> bool {
292        self.find_class(internal)
293            .map(|pc| pc.is_interface())
294            .unwrap_or(false)
295    }
296
297    fn is_sealed(&self, _internal: &str) -> bool {
298        false
299    }
300
301    fn super_name(&self, internal: &str) -> Option<String> {
302        self.find_class(internal)
303            .and_then(|pc| pc.super_name.clone())
304    }
305
306    fn has_class(&self, internal: &str) -> bool {
307        self.pool.get(internal).is_some()
308            || internal == self.class.name
309            // The ddcroot ROOT-PACKAGE RELOCATION only: the display
310            // prepends the synthetic package, the pool key lacks it.
311            // Deliberately NOT a general un-rename: collision-renamed
312            // nested families (weibo x0$a$b) render dotted against
313            // their renamed emission owners — a general un-rename
314            // flipped them to flat `$` renders (weibo 5,357 → 77,783
315            // cannot-find cascade). Flat `$` here is right ONLY for
316            // relocated root classes, which ARE their own emission
317            // unit (lark UserCustomStatusExtraParams$* "不可见" ×138).
318            || crate::root_pkg_display().is_some_and(|rp| {
319                internal.len() > rp.len() + 1
320                    && internal.starts_with(rp.as_str())
321                    && internal.as_bytes()[rp.len()] == b'/'
322                    && self.pool.get(&internal[rp.len() + 1..]).is_some()
323            })
324    }
325
326    fn class_supers_args(
327        &self,
328        internal: &str,
329        _args: &[GenericType],
330    ) -> Vec<(String, Vec<GenericType>)> {
331        let mut out = Vec::new();
332        let mut cur = self.find_class(internal).map(|pc| pc.name.clone());
333        let mut hops = 0;
334        while let Some(name) = cur {
335            hops += 1;
336            if hops > 64 {
337                break;
338            }
339            if name == "java/lang/Object" {
340                break;
341            }
342            out.push((name.clone(), Vec::new()));
343            cur = self.find_class(&name).and_then(|pc| pc.super_name.clone());
344        }
345        out
346    }
347
348    fn class_bases(&self, internal: &str) -> Option<(Vec<String>, Option<String>)> {
349        let pc = self.find_class(internal)?;
350        Some((pc.interfaces.clone(), pc.super_name.clone()))
351    }
352
353    fn field_flags(&self, internal: &str, name: &str) -> Option<FieldAccessFlags> {
354        let pc = self.find_class(internal)?;
355        let raw = pc
356            .static_fields
357            .iter()
358            .chain(pc.instance_fields.iter())
359            .find(|f| f.name.as_ref() == name)?
360            .access;
361        Some(raw_field_flags(raw))
362    }
363
364    fn method_flags(&self, internal: &str, name: &str, desc: &str) -> Option<MethodAccessFlags> {
365        let pc = self.find_class(internal)?;
366        let m = pc.find_method(name, desc)?;
367        Some(raw_method_flags(m.access))
368    }
369
370    fn declares_field(&self, internal: &str, name: &str) -> bool {
371        if internal == self.class.name {
372            return self.inherited_fields.contains(name)
373                || self.class.field_flags_of(name).is_some();
374        }
375        self.find_class(internal)
376            .map(|pc| pc.field_flags_of(name).is_some())
377            .unwrap_or(false)
378    }
379
380    fn is_fw_shadow(&self, internal: &str) -> bool {
381        crate::is_fw_shadow(internal)
382    }
383
384    fn declares_field_display(&self, internal: &str, display: &str) -> bool {
385        let pc = if internal == self.class.name {
386            self.class
387        } else {
388            match self.find_class(internal) {
389                Some(pc) => pc,
390                None => return false,
391            }
392        };
393        pc.static_fields
394            .iter()
395            .chain(pc.instance_fields.iter())
396            .any(|f| {
397                let disp = jdc_core::rename::field_display(internal, &f.name, &f.desc)
398                    .map(|d| d.to_string())
399                    .unwrap_or_else(|| crate::classdec::java_ident(&f.name).into_owned());
400                disp == display
401            })
402    }
403
404    fn declares_method_named(&self, internal: &str, name: &str) -> bool {
405        self.find_class(internal)
406            .map(|pc| pc.all_methods().any(|m| &*m.name == name))
407            .unwrap_or(false)
408    }
409
410    fn is_generic_call(&self, _e: &Expr) -> bool {
411        false
412    }
413
414    fn generic_call_formals(&self, _e: &Expr) -> Option<(Vec<GenericType>, Vec<String>)> {
415        None
416    }
417
418    fn polymorphic_ret_cast(
419        &self,
420        _cls: &str,
421        _name: &str,
422        _desc: &MethodDescriptor,
423    ) -> Option<JavaType> {
424        None
425    }
426
427    fn ctor_formals_by_arity(&self, internal: &str, arity: usize) -> Option<Vec<GenericType>> {
428        let pc = self.find_class(internal)?;
429        let ctors = pc.ctors_by_arity(arity);
430        let m = ctors.first()?;
431        let md = m.parsed_desc()?;
432        Some(md.args.iter().map(java_type_to_generic).collect())
433    }
434
435    fn ctor_param_types(
436        &self,
437        internal: &str,
438        skip: usize,
439        n: usize,
440        args: &[Expr],
441    ) -> Option<Vec<JavaType>> {
442        let _ = args;
443        let pc = self.find_class(internal)?;
444        let ctors = pc.ctors_by_arity(skip + n);
445        let m = ctors.first()?;
446        let md = m.parsed_desc()?;
447        Some(md.args.iter().skip(skip).cloned().collect())
448    }
449
450    fn outer_param_via_super(&self, _internal: &str) -> bool {
451        false
452    }
453
454    fn nested_method(
455        &self,
456        _l: &jdc_core::ir::expr::LambdaExpr,
457        _outer_vt: &jdc_core::var::VarTable,
458    ) -> Option<jdc_core::MethodBody> {
459        None
460    }
461}
462
463/// `$`-suffix classification (javac conventions d8 preserves).
464fn classify_nested(rest: &str) -> NestedKind {
465    // rest is the text after `Outer$`.
466    let tail = rest.rsplit('$').next().unwrap_or(rest);
467    if rest.starts_with("-$$Lambda$") || tail.starts_with("-$$Lambda$") {
468        return NestedKind::Lambda;
469    }
470    if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
471        return NestedKind::Anonymous;
472    }
473    if tail.starts_with(|c: char| c.is_ascii_digit()) {
474        return NestedKind::Local;
475    }
476    NestedKind::Member
477}
478
479fn raw_field_flags(raw: u32) -> FieldAccessFlags {
480    let mut f = FieldAccessFlags::empty();
481    if raw & ACC_PUBLIC != 0 {
482        f |= FieldAccessFlags::PUBLIC;
483    }
484    if raw & ACC_PRIVATE != 0 {
485        f |= FieldAccessFlags::PRIVATE;
486    }
487    if raw & ACC_PROTECTED != 0 {
488        f |= FieldAccessFlags::PROTECTED;
489    }
490    if raw & ACC_STATIC != 0 {
491        f |= FieldAccessFlags::STATIC;
492    }
493    if raw & ACC_FINAL != 0 {
494        f |= FieldAccessFlags::FINAL;
495    }
496    if raw & ACC_SYNTHETIC != 0 {
497        f |= FieldAccessFlags::SYNTHETIC;
498    }
499    if raw & ACC_ENUM != 0 {
500        f |= FieldAccessFlags::ENUM;
501    }
502    f
503}
504
505fn raw_method_flags(raw: u32) -> MethodAccessFlags {
506    let mut f = MethodAccessFlags::empty();
507    if raw & ACC_PUBLIC != 0 {
508        f |= MethodAccessFlags::PUBLIC;
509    }
510    if raw & ACC_PRIVATE != 0 {
511        f |= MethodAccessFlags::PRIVATE;
512    }
513    if raw & ACC_PROTECTED != 0 {
514        f |= MethodAccessFlags::PROTECTED;
515    }
516    if raw & ACC_STATIC != 0 {
517        f |= MethodAccessFlags::STATIC;
518    }
519    if raw & ACC_FINAL != 0 {
520        f |= MethodAccessFlags::FINAL;
521    }
522    if raw & ACC_SYNCHRONIZED != 0 || raw & ACC_DECLARED_SYNCHRONIZED != 0 {
523        f |= MethodAccessFlags::SYNCHRONIZED;
524    }
525    if raw & ACC_BRIDGE != 0 {
526        f |= MethodAccessFlags::BRIDGE;
527    }
528    if raw & ACC_VARARGS != 0 {
529        f |= MethodAccessFlags::VARARGS;
530    }
531    if raw & ACC_NATIVE != 0 {
532        f |= MethodAccessFlags::NATIVE;
533    }
534    if raw & ACC_ABSTRACT != 0 {
535        f |= MethodAccessFlags::ABSTRACT;
536    }
537    if raw & ACC_STRICT != 0 {
538        f |= MethodAccessFlags::STRICT;
539    }
540    if raw & ACC_SYNTHETIC != 0 {
541        f |= MethodAccessFlags::SYNTHETIC;
542    }
543    f
544}
545
546/// Erased JavaType → generic type view (no signature data in DEX).
547pub fn java_type_to_generic(t: &JavaType) -> GenericType {
548    match t {
549        JavaType::Object(n) => GenericType::Class(class_sig_of(n)),
550        JavaType::Array(inner) => GenericType::Array(Box::new(java_type_to_generic(inner))),
551        other => GenericType::Primitive(other.primitive_char().unwrap_or('V')),
552    }
553}
554
555fn class_sig_of(internal: &str) -> jdc_core::types::ClassSig {
556    let (package, name) = match internal.rfind('/') {
557        Some(i) => (internal[..i].to_string(), internal[i + 1..].to_string()),
558        None => (String::new(), internal.to_string()),
559    };
560    jdc_core::types::ClassSig {
561        package,
562        parts: vec![jdc_core::types::ClassSigPart { name, args: vec![] }],
563    }
564}
565
566/// Render a type reference for signatures where the printer is not used.
567pub fn type_ref_of(desc: &str) -> TypeRef {
568    TypeRef::J(desc_type(desc))
569}
570
571/// Pool-level mirror of the `DexCtx::nested_is_static` render decision
572/// for passes that run without a ctx (method.rs). ACC_STATIC annotation
573/// evidence OR the structural signals — no outer-typed `this$0` field,
574/// or no formal-0 outer ctor param (a this$0 field without the formal-0
575/// param is a Kotlin lambda capture, not a JLS outer).
576pub fn nested_is_static(pool: &DexPool, pc: &PoolClass) -> bool {
577    if pc.is_static_nested() {
578        return true;
579    }
580    let Some(outer) = crate::find_outer_name(pool, &pc.name) else {
581        return true;
582    };
583    let holds = pc.instance_fields.iter().any(|f| {
584        f.name.as_ref() == "this$0"
585            && f
586                .desc
587                .strip_prefix('L')
588                .and_then(|d| d.strip_suffix(';'))
589                .is_some_and(|ty| ty == outer)
590    });
591    if !holds {
592        return true;
593    }
594    !pc.all_methods().any(|m| {
595        &*m.name == "<init>"
596            && m.parsed_desc().is_some_and(|d| {
597                d.args.first().is_some_and(|t| {
598                    matches!(t, JavaType::Object(n) if n.as_ref() == outer.as_str())
599                })
600            })
601    })
602}