1use jdc_core::emit::{escape_string, format_float, Printer};
5use jdc_core::types::JavaType;
6use jdc_core::Ctx;
7
8use crate::access::*;
9use crate::ctx::{java_type_to_generic, DexCtx};
10use crate::method::decompile_method;
11use crate::{desc_type, DexPool, PoolClass, PoolMethod, StaticValue};
12
13#[derive(Debug, Clone)]
14pub struct ClassOptions {
15 pub provenance: bool,
17}
18
19impl Default for ClassOptions {
20 fn default() -> Self {
21 ClassOptions { provenance: true }
22 }
23}
24
25pub fn decompile_class(
32 pool: &std::sync::Arc<DexPool>,
33 class: &PoolClass,
34 opts: &ClassOptions,
35 pending: &std::sync::Mutex<
36 Vec<(
37 std::sync::mpsc::Receiver<Result<String, String>>,
38 String,
39 std::time::Instant,
40 )>,
41 >,
42) -> anyhow::Result<String> {
43 if class_is_risky(pool, class) {
44 let pool2 = pool.clone();
48 let cls = class.clone();
49 let opts2 = opts.clone();
50 let (tx, rx) = std::sync::mpsc::channel::<Result<String, String>>();
51 let name = class.name.clone();
52 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
53 let _ = std::thread::Builder::new()
54 .stack_size(64 * 1024 * 1024)
55 .spawn(move || {
56 let r = decompile_class_impl(&pool2, &cls, &opts2).map_err(|e| format!("{:#}", e));
57 let _ = tx.send(r);
58 });
59 pending.lock().unwrap().push((rx, name, deadline));
60 return Err(anyhow::anyhow!(
61 "deferred to monitored thread (pathological-CFG guard)"
62 ));
63 }
64 decompile_class_impl(pool, class, opts)
65}
66
67fn class_is_risky(pool: &DexPool, class: &PoolClass) -> bool {
72 for m in class.all_methods() {
75 if m.code_off == 0 {
76 continue;
77 }
78 if let Some(dex) = pool.dex(m.dex_idx) {
79 if let Some((_regs, insns)) = dex.code_stats(m.code_off) {
80 if insns > 16_000 {
81 return true;
82 }
83 }
84 }
85 }
86 false
87}
88
89fn decompile_class_impl(
90 pool: &DexPool,
91 class: &PoolClass,
92 opts: &ClassOptions,
93) -> anyhow::Result<String> {
94 let ctx = DexCtx::new(pool, class);
95 let mut out = String::new();
96 if opts.provenance {
97 out.push_str(&format!(
98 "// Decompiled by https://github.com/ejfkdev/ddc {}\n",
99 env!("CARGO_PKG_VERSION")
100 ));
101 let label = pool
105 .dex_labels
106 .get(class.dex_idx)
107 .cloned()
108 .unwrap_or_default();
109 if let Some(dex) = pool.dex(class.dex_idx) {
110 out.push_str(&format!("// From: {} (DEX {})\n", label, dex.version));
111 }
112 }
113 if let Some(src) = &class.source_file {
114 out.push_str(&format!("// Source file: {}\n", src));
115 }
116 if class.is_synthetic() {
117 out.push_str("// synthetic\n");
118 }
119 let (pkg, _) = split_name(&class.name);
120 if !pkg.is_empty() {
121 out.push('\n');
122 out.push_str(&format!("package {};\n", pkg.replace('/', ".")));
123 }
124 out.push('\n');
125 let mut body = String::new();
126 emit_class_body(pool, class, &ctx, opts, &mut body, 0)?;
127 out.push_str(&body);
128 Ok(out)
129}
130
131fn emit_class_body(
134 pool: &DexPool,
135 class: &PoolClass,
136 ctx: &DexCtx<'_>,
137 opts: &ClassOptions,
138 out: &mut String,
139 depth: usize,
140) -> anyhow::Result<()> {
141 let ind = indent(depth);
142 let (_, simple) = split_name(&class.name);
143 let simple = simple.replace('$', ".");
144 let is_iface = class.is_interface();
145 let is_enum = class.is_enum();
146
147 let mut head = String::new();
148 let a = class.access;
149 if a & ACC_PUBLIC != 0 {
150 head.push_str("public ");
151 }
152 if a & ACC_FINAL != 0 && !is_enum {
153 head.push_str("final ");
154 }
155 if a & ACC_ABSTRACT != 0 && !is_iface {
156 head.push_str("abstract ");
157 }
158 if a & ACC_ANNOTATION != 0 {
159 head.push_str("@");
160 }
161 if is_enum {
162 head.push_str("/* enum */ final class ");
163 head.push_str(&simple);
164 } else if is_iface {
165 head.push_str("interface ");
166 head.push_str(&simple);
167 } else {
168 head.push_str("class ");
169 head.push_str(&simple);
170 }
171 if is_iface {
172 if !class.interfaces.is_empty() {
173 head.push_str(" extends ");
174 head.push_str(&join_dotted(&class.interfaces));
175 }
176 } else {
177 if let Some(sup) = &class.super_name {
178 if sup != "java/lang/Object" && !is_enum {
179 head.push_str(" extends ");
180 head.push_str(&dotted(sup));
181 }
182 }
183 if !class.interfaces.is_empty() {
184 head.push_str(if is_iface {
185 " extends "
186 } else {
187 " implements "
188 });
189 head.push_str(&join_dotted(&class.interfaces));
190 }
191 }
192
193 out.push_str(&ind);
194 out.push_str(&head);
195 out.push_str(" {\n");
196
197 for (i, f) in class.static_fields.iter().enumerate() {
199 if i > 0 || !class.instance_fields.is_empty() {
200 out.push('\n');
201 }
202 emit_field(
203 pool,
204 f,
205 class.static_values.get(i),
206 &class.name,
207 out,
208 depth + 1,
209 true,
210 );
211 }
212 if !class.instance_fields.is_empty() && !class.static_fields.is_empty() {
213 out.push('\n');
214 }
215 for (i, f) in class.instance_fields.iter().enumerate() {
216 if i > 0 {
217 out.push('\n');
218 }
219 emit_field(pool, f, None, &class.name, out, depth + 1, false);
220 }
221
222 let mut emitted_any = !class.static_fields.is_empty() || !class.instance_fields.is_empty();
224 for m in class.all_methods() {
225 if m.name == "<clinit>" {
226 continue; }
228 let text = emit_method(pool, class, ctx, m, depth + 1)?;
229 if let Some(text) = text {
230 if emitted_any {
231 out.push('\n');
232 }
233 out.push_str(&text);
234 emitted_any = true;
235 }
236 }
237 if let Some(clinit) = class.all_methods().find(|m| m.name == "<clinit>") {
239 if let Some(text) = emit_method(pool, class, ctx, clinit, depth + 1)? {
240 if emitted_any {
241 out.push('\n');
242 }
243 out.push_str(&text);
244 emitted_any = true;
245 }
246 }
247
248 for nested in nested_members(pool, class, ctx) {
251 if emitted_any {
252 out.push('\n');
253 }
254 let nested_ctx = DexCtx::new(pool, nested);
255 out.push('\n');
256 emit_class_body(pool, nested, &nested_ctx, opts, out, depth + 1)?;
257 emitted_any = true;
258 }
259
260 out.push_str(&ind);
261 out.push_str("}\n");
262 Ok(())
263}
264
265fn nested_members<'a>(
268 pool: &'a DexPool,
269 class: &'a PoolClass,
270 ctx: &DexCtx<'_>,
271) -> Vec<&'a PoolClass> {
272 let mut out = Vec::new();
273 for name in pool.children_of(&class.name) {
274 let Some(pc) = pool.get(&name) else { continue };
275 let rest = &name[class.name.len() + 1..];
276 if rest.is_empty() || rest.starts_with('-') {
277 continue;
278 }
279 if ctx.find_outer(&name).as_deref() != Some(class.name.as_str()) {
280 continue;
281 }
282 let tail = rest.rsplit('$').next().unwrap_or(rest);
283 if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
284 continue; }
286 if tail.starts_with(|c: char| c.is_ascii_digit()) {
287 continue; }
289 out.push(pc);
290 }
291 out
292}
293
294fn emit_field(
295 pool: &DexPool,
296 f: &crate::PoolField,
297 init: Option<&StaticValue>,
298 f_class: &str,
299 out: &mut String,
300 depth: usize,
301 is_static: bool,
302) {
303 let ind = indent(depth);
304 let mut line = String::new();
305 let a = f.access;
306 if a & ACC_PUBLIC != 0 {
307 line.push_str("public ");
308 } else if a & ACC_PRIVATE != 0 {
309 line.push_str("private ");
310 } else if a & ACC_PROTECTED != 0 {
311 line.push_str("protected ");
312 }
313 if is_static {
314 line.push_str("static ");
315 }
316 if a & ACC_FINAL != 0 {
317 line.push_str("final ");
318 }
319 if a & ACC_SYNTHETIC != 0 {
320 line.push_str("/* synthetic */ ");
321 }
322 if a & ACC_TRANSIENT_HINT != 0 {
323 line.push_str("transient ");
324 }
325 if a & ACC_VOLATILE_HINT != 0 {
326 line.push_str("volatile ");
327 }
328 let ty = type_name(pool, &desc_type(&f.desc));
329 line.push_str(&ty);
330 line.push(' ');
331 line.push_str(&f.name);
332 if let Some(v) = init {
333 if let Some(text) = render_static_value(v, &f_class) {
334 line.push_str(" = ");
335 line.push_str(&text);
336 }
337 }
338 line.push(';');
339 out.push_str(&ind);
340 out.push_str(&line);
341 out.push('\n');
342}
343
344pub const ACC_VOLATILE_HINT: u32 = 0x40;
347pub const ACC_TRANSIENT_HINT: u32 = 0x80;
348
349fn render_static_value(v: &StaticValue, owner: &str) -> Option<String> {
350 Some(match v {
351 StaticValue::Int(i) => i.to_string(),
352 StaticValue::Float(f) => format_float(*f as f64, true),
353 StaticValue::Double(d) => format_float(*d, false),
354 StaticValue::Str(s) => format!("\"{}\"", escape_string(s)),
355 StaticValue::Type(t) => format!("{}.class", dotted(t)),
356 StaticValue::Boolean(b) => b.to_string(),
357 StaticValue::Null => "null".into(),
358 StaticValue::Field(cls, name) => {
359 if cls == owner {
360 name.clone()
361 } else {
362 format!("{}.{}", dotted(cls), name)
363 }
364 }
365 StaticValue::Other => return None,
366 })
367}
368
369fn emit_method(
370 pool: &DexPool,
371 class: &PoolClass,
372 ctx: &DexCtx<'_>,
373 m: &PoolMethod,
374 depth: usize,
375) -> anyhow::Result<Option<String>> {
376 let ind = indent(depth);
377 let desc = m.parsed_desc();
378
379 let mut sig = String::new();
381 let a = m.access;
382 if a & ACC_PUBLIC != 0 {
383 sig.push_str("public ");
384 } else if a & ACC_PRIVATE != 0 {
385 sig.push_str("private ");
386 } else if a & ACC_PROTECTED != 0 {
387 sig.push_str("protected ");
388 }
389 let is_clinit = m.name == "<clinit>";
390 let is_init = m.name == "<init>";
391 if a & ACC_STATIC != 0 || is_clinit {
392 sig.push_str("static ");
393 }
394 if a & ACC_FINAL != 0 {
395 sig.push_str("final ");
396 }
397 if a & (ACC_SYNCHRONIZED | ACC_DECLARED_SYNCHRONIZED) != 0 {
398 sig.push_str("synchronized ");
399 }
400 if a & ACC_NATIVE != 0 {
401 sig.push_str("native ");
402 }
403 if a & ACC_ABSTRACT != 0 {
404 sig.push_str("abstract ");
405 }
406 if a & ACC_SYNTHETIC != 0 {
407 sig.push_str("/* synthetic */ ");
408 }
409
410 let body = decompile_method(pool, class, m).ok().flatten();
412 let param_names: Vec<String> = body
413 .as_ref()
414 .map(|b| {
415 let mut ps: Vec<(u16, String)> =
416 b.vt.vars
417 .iter()
418 .filter(|v| v.is_param && v.name != "this")
419 .map(|v| (v.slot, v.name.clone()))
420 .collect();
421 ps.sort_by_key(|(s, _)| *s);
422 ps.into_iter().map(|(_, n)| n).collect()
423 })
424 .unwrap_or_else(|| {
425 (0..desc.as_ref().map(|d| d.args.len()).unwrap_or(0))
426 .map(|i| format!("p{}", i + 1))
427 .collect()
428 });
429
430 if is_clinit {
431 } else {
433 let Some(d) = &desc else { return Ok(None) };
434 if is_init {
435 let (_, simple) = split_name(&class.name);
436 sig.push_str(&simple.replace('$', "."));
437 } else {
438 sig.push_str(&type_name(pool, &d.ret));
439 sig.push(' ');
440 sig.push_str(&m.name);
441 }
442 sig.push('(');
443 let n = d.args.len();
444 let varargs = a & ACC_VARARGS != 0 && n > 0;
445 for (i, arg) in d.args.iter().enumerate() {
446 if i > 0 {
447 sig.push_str(", ");
448 }
449 let name = param_names
450 .get(i)
451 .cloned()
452 .unwrap_or_else(|| format!("p{}", i));
453 if varargs && i + 1 == n {
454 if let JavaType::Array(inner) = arg {
455 sig.push_str(&type_name(pool, inner));
456 sig.push_str("...");
457 } else {
458 sig.push_str(&type_name(pool, arg));
459 }
460 } else {
461 sig.push_str(&type_name(pool, arg));
462 }
463 sig.push(' ');
464 sig.push_str(&name);
465 }
466 sig.push(')');
467 }
468
469 if is_clinit {
470 let Some(b) = body else { return Ok(None) };
471 let printer = Printer::new(ctx, &b.vt);
472 let t_print = std::time::Instant::now();
473 let body_text = printer.into_string(&b.body);
474 crate::method::phase_hit(3, t_print);
475 let mut out = String::new();
476 out.push_str(&ind);
477 out.push_str("static {\n");
478 for line in body_text.lines() {
479 if line.is_empty() {
480 out.push('\n');
481 } else {
482 out.push_str(&ind);
483 out.push_str(" ");
484 out.push_str(line);
485 out.push('\n');
486 }
487 }
488 out.push_str(&ind);
489 out.push_str("}\n");
490 return Ok(Some(out));
491 }
492 if a & (ACC_ABSTRACT | ACC_NATIVE) != 0 || body.is_none() {
493 return Ok(Some(format!("{}{};\n", ind, sig)));
494 }
495 let Some(b) = body else { return Ok(None) };
496
497 let mut printer = Printer::new(ctx, &b.vt);
500 match &b.desc.ret {
501 JavaType::Boolean => {
502 printer = printer.with_ret_bool(true);
503 }
504 JavaType::Char => {
505 printer = printer.with_ret_char(true);
506 }
507 JavaType::Byte => {
508 printer = printer.with_ret_narrow(true, false);
509 }
510 JavaType::Short => {
511 printer = printer.with_ret_narrow(false, true);
512 }
513 _ => {}
514 }
515 let t_print = std::time::Instant::now();
516 let body_text = printer.into_string(&b.body);
517 crate::method::phase_hit(3, t_print);
518
519 let mut out = String::new();
520 out.push_str(&ind);
521 out.push_str(&sig);
522 out.push_str(" {\n");
523 if !body_text.trim().is_empty() {
524 for line in body_text.lines() {
525 if line.is_empty() {
526 out.push('\n');
527 } else {
528 out.push_str(&ind);
529 out.push_str(" ");
530 out.push_str(line);
531 out.push('\n');
532 }
533 }
534 }
535 out.push_str(&ind);
536 out.push_str("}\n");
537 Ok(Some(out))
538}
539
540fn indent(depth: usize) -> String {
545 " ".repeat(depth)
546}
547
548fn split_name(internal: &str) -> (String, String) {
549 match internal.rfind('/') {
550 Some(i) => (internal[..i].to_string(), internal[i + 1..].to_string()),
551 None => (String::new(), internal.to_string()),
552 }
553}
554
555pub fn dotted(internal: &str) -> String {
557 internal.replace('/', ".").replace('$', ".")
558}
559
560fn join_dotted(names: &[String]) -> String {
561 names
562 .iter()
563 .map(|n| dotted(n))
564 .collect::<Vec<_>>()
565 .join(", ")
566}
567
568pub fn type_name(pool: &DexPool, t: &JavaType) -> String {
571 match t {
572 JavaType::Void => "void".into(),
573 JavaType::Object(n) => print_class_name(pool, n),
574 JavaType::Array(inner) => format!("{}[]", type_name(pool, inner)),
575 other => other.to_java(false),
576 }
577}
578
579pub fn print_class_name(pool: &DexPool, internal: &str) -> String {
582 let mut out = String::new();
583 let mut rest: &str = internal;
584 loop {
585 match rest.find('$') {
586 Some(i) => {
587 let cand = &rest[..i];
588 let known = pool.get(cand).is_some() || cand == internal;
589 out.push_str(&cand.replace('/', "."));
590 out.push_str(if known { "." } else { "$" });
591 rest = &rest[i + 1..];
592 }
593 None => {
594 out.push_str(&rest.replace('/', "."));
595 return out;
596 }
597 }
598 }
599}
600
601pub fn dotted_pool(pool: &DexPool, internal: &str) -> String {
603 print_class_name(pool, internal)
604}
605
606#[allow(dead_code)]
608fn _unused(_: &dyn Fn(&JavaType) -> jdc_core::types::GenericType) {
609 let _ = java_type_to_generic;
610}